当前位置: 首页 > news >正文

网站建设置顶形象墙在线设计网站

网站建设置顶,形象墙在线设计网站,网站服务器过期了怎么办,网络舆情监测机制目录 一、数组 / 字符串 1.合并两个有序数组 #xff08;简单#xff09; 2.移除元素 #xff08;简单#xff09; 3.删除有序数组中的重复项 #xff08;简单#xff09; 4.删除有序数组中的重复项 II#xff08;中等#xff09; 5.多数元素#xff08;简单简单 2.移除元素 简单 3.删除有序数组中的重复项 简单 4.删除有序数组中的重复项 II中等 5.多数元素简单 6.轮转数组 中等 7.买卖股票的最佳时机简单 8.买卖股票的最佳时机 II 中等 9.跳跃游戏中等 10.跳跃游戏 II中等 11.H 指数中等 12.O(1) 时间插入、删除和获取随机元素中等 13.除自身以外数组的乘积中等 14.加油站中等 15.发糖果困难 16.接雨水 困难 17.罗马数字转整数简单 18.整数转罗马数字中等 19.最后一个单词的长度简单 20.最长公共前缀简单 21.反转字符串中的单词中等 22.Z 字形变换中等 23.找出字符串中第一个匹配项的下标 简单 24.文本左右对齐困难 二、双指针 25.验证回文串简单 26.判断子序列简单 27.两数之和 II - 输入有序数组中等 28.盛最多水的容器中等 29.三数之和中等 三、滑动窗口 30.长度最小的子数组中等 31.无重复字符的最长子串中等 32.串联所有单词的子串困难 33.最小覆盖子串困难 四、矩阵 34.有效的数独中等 35.螺旋矩阵中等 36.旋转图像中等 37.矩阵置零 中等 38.生命游戏中等 五、哈希表 39.赎金信简单 40.同构字符串简单 41.单词规律简单 42.有效的字母异位词简单 43.字母异位词分组中等 44.两数之和简单 45.快乐数简单 46.存在重复元素 II简单 47.最长连续序列中等 六、区间 48.汇总区间简单 49.合并区间中等 50.插入区间中等 51.用最少数量的箭引爆气球中等 七、栈 52.有效的括号简单 53.简化路径中等 54.最小栈中等 55.逆波兰表达式求值中等 56.基本计算器困难 八、链表 57.环形链表简单 58.两数相加中等 59.合并两个有序链表简单 60.随机链表的复制中等 61.反转链表 II中等 62.K 个一组翻转链表 困难 63.删除链表的倒数第 N 个结点中等 64.删除排序链表中的重复元素 II中等 65.旋转链表中等 66.分隔链表中等 67.LRU 缓存中等 九、二叉树 68.二叉树的最大深度简单 69.相同的树简单 70.翻转二叉树简单 71.对称二叉树简单 72.从前序与中序遍历序列构造二叉树中等 73. 从中序与后序遍历序列构造二叉树中等 74.填充每个节点的下一个右侧节点指针 II中等 75.二叉树展开为链表中等 76.路径总和简单 77.求根节点到叶节点数字之和中等 78.二叉树中的最大路径和 困难 79.二叉搜索树迭代器中等 80.完全二叉树的节点个数简单 81.二叉树的最近公共祖先中等 十、二叉树层次遍历 82.二叉树的右视图中等 83.二叉树的层平均值简单 84.二叉树的层序遍历中等 85.二叉树的锯齿形层序遍历中等 十一、二叉搜索树 86.二叉搜索树的最小绝对差简单 87.二叉搜索树中第 K 小的元素中等 88.验证二叉搜索树中等 十二、图 89.岛屿数量中等 90.被围绕的区域中等 91.克隆图中等 92.除法求值中等 93.课程表中等 94.课程表 II中等 十三、图的广度优先搜索 95.蛇梯棋中等 96.最小基因变化中等 97.单词接龙困难 十四、字典树 98.实现 Trie (前缀树)中等 99.添加与搜索单词 - 数据结构设计中等 100.单词搜索 II困难 十五、回溯 101.电话号码的字母组合中等 102.组合 中等 103.全排列中等 104.组合总和中等 105.N 皇后 II困难 106.括号生成中等 107.单词搜索中等 十六、分治 108.将有序数组转换为二叉搜索树简单 109.排序链表中等 110.建立四叉树中等 111.合并 K 个升序链表 困难 十七、Kadane 算法 112.最大子数组和 中等 113.环形子数组的最大和 十八、二分查找 114.搜索插入位置简单 115.搜索二维矩阵中等 116.寻找峰值中等 117.搜索旋转排序数组中等 118.在排序数组中查找元素的第一个和最后一个位置中等 119.寻找旋转排序数组中的最小值中等 120.寻找两个正序数组的中位数 困难 十九、堆 121.数组中的第K个最大元素中等 122.IPO困难 123.查找和最小的 K 对数字中等 124.数据流的中位数困难 二十、位运算 125.二进制求和简单 126.颠倒二进制位简单 127.位1的个数简单 128.只出现一次的数字简单 129.只出现一次的数字 II中等 130.数字范围按位与中等 二十一、数学 131.回文数简单 132.加一简单 133. 阶乘后的零中等 134.x 的平方根 中等 135.Pow(x, n)中等 136.直线上最多的点数困难 二十二、一维动态规划 137.爬楼梯简单 138.打家劫舍中等 139.单词拆分 中等 140.零钱兑换中等 141.最长递增子序列  中等 二十三、多维动态规划 142.三角形最小路径和中等 143.最小路径和中等 144.不同路径 II中等 145.最长回文子串中等 146.交错字符串中等 147.编辑距离中等 148.买卖股票的最佳时机 III困难 149.买卖股票的最佳时机 IV困难 150.最大正方形中等 干货分享感谢您的阅读 一、数组 / 字符串 1.合并两个有序数组 简单 题目描述 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2另有两个整数 m 和 n 分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中使合并后的数组同样按 非递减顺序 排列。 注意最终合并后数组不应由函数返回而是存储在数组 nums1 中。为了应对这种情况nums1 的初始长度为 m n其中前 m 个元素表示应合并的元素后 n 个元素为 0 应忽略。nums2 的长度为 n 。 示例 1输入nums1 [1,2,3,0,0,0], m 3, nums2 [2,5,6], n 3 输出[1,2,2,3,5,6] 解释需要合并 [1,2,3] 和 [2,5,6] 。 合并结果是 [1,2,2,3,5,6] 其中斜体加粗标注的为 nums1 中的元素。 示例 2输入nums1 [1], m 1, nums2 [], n 0 输出[1] 解释需要合并 [1] 和 [] 。 合并结果是 [1] 。 示例 3输入nums1 [0], m 0, nums2 [1], n 1 输出[1] 解释需要合并的数组是 [] 和 [1] 。 合并结果是 [1] 。 注意因为 m 0 所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。 提示 nums1.length m nnums2.length n0 m, n 2001 m n 200-  nums1[i], nums2[j]   进阶你可以设计实现一个时间复杂度为 O(m n) 的算法解决此问题吗 解题思路 为了合并两个有序数组 nums1 和 nums2并保持合并后的数组依然有序可以使用双指针从后向前的方式合并数组。因为 nums1 数组的末尾已经预留了足够的空间m n 大小可以避免在合并过程中移动大量元素。 指针初始化初始化三个指针p1 指向 nums1 的有效元素的最后一个位置即 m - 1p2 指向 nums2 的最后一个元素即 n - 1p 指向合并后数组的最后一个位置即 m n - 1。 从后向前比较从数组的末尾开始比较 nums1[p1] 和 nums2[p2] 的大小将较大的值放在 nums1[p] 处然后移动对应的指针重复上述步骤直到其中一个数组的所有元素都已被合并。 处理剩余元素如果 nums2 中还有未合并的元素需要将它们全部复制到 nums1 的前部。这是因为 nums1 中的前部位置可能已经填满了所有来自 nums1 的元素剩余位置应由 nums2 的元素填充。 复杂度分析 该算法的时间复杂度为 O(m n)因为我们只需遍历两个数组各一次即可完成合并。空间复杂度为 O(1)因为合并过程是在 nums1 原地进行的没有使用额外的空间。 代码实现 package org.zyf.javabasic.letcode.jd150.stringarray;/*** program: zyfboot-javabasic* description: 合并两个有序数组* author: zhangyanfeng* create: 2024-08-24 19:43**/ public class Merge {public void merge(int[] nums1, int m, int[] nums2, int n) {// 初始化三个指针int p1 m - 1; // 指向nums1的有效元素末尾int p2 n - 1; // 指向nums2的末尾int p m n - 1; // 指向合并后数组的末尾// 从后向前遍历合并while (p1 0 p2 0) {// 比较nums1[p1]和nums2[p2]将较大值放在nums1[p]if (nums1[p1] nums2[p2]) {nums1[p] nums1[p1];p1--; // 移动指针p1} else {nums1[p] nums2[p2];p2--; // 移动指针p2}p--; // 移动指针p}// 如果nums2还有剩余元素则将其复制到nums1前部while (p2 0) {nums1[p] nums2[p2];p2--;p--;}}public static void main(String[] args) {Merge solution new Merge();int[] nums1 {1, 2, 3, 0, 0, 0};int m 3;int[] nums2 {2, 5, 6};int n 3;solution.merge(nums1, m, nums2, n);// 输出合并后的nums1for (int num : nums1) {System.out.print(num );}// 输出结果应为: 1 2 2 3 5 6} }2.移除元素 简单 题目描述 给你一个数组 nums 和一个值 val你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。 假设 nums 中不等于 val 的元素数量为 k要通过此题您需要执行以下操作 更改 nums 数组使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要。返回 k。 用户评测 评测机将使用以下代码测试您的解决方案 int[] nums [...]; // 输入数组 int val ...; // 要移除的值 int[] expectedNums [...]; // 长度正确的预期答案。// 它以不等于 val 的值排序。int k removeElement(nums, val); // 调用你的实现assert k expectedNums.length; sort(nums, 0, k); // 排序 nums 的前 k 个元素 for (int i 0; i actualLength; i) {assert nums[i] expectedNums[i]; } 如果所有的断言都通过你的解决方案将会 通过。 示例 1输入nums [3,2,2,3], val 3 输出2, nums [2,2,_,_] 解释你的函数函数应该返回 k 2, 并且 nums 中的前两个元素均为 2。 你在返回的 k 个元素之外留下了什么并不重要因此它们并不计入评测。 示例 2输入nums [0,1,2,2,3,0,4,2], val 2 输出5, nums [0,1,4,0,3,_,_,_] 解释你的函数应该返回 k 5并且 nums 中的前五个元素为 0,0,1,3,4。 注意这五个元素可以任意顺序返回。 你在返回的 k 个元素之外留下了什么并不重要因此它们并不计入评测。 提示 0 nums.length 1000 nums[i] 500 val 100 解题思路 直接见数组知识及编程练习总结-CSDN博客中第17题。 3.删除有序数组中的重复项 简单 题目描述 给你一个 非严格递增排列 的数组 nums 请你 原地 删除重复出现的元素使每个元素 只出现一次 返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。 考虑 nums 的唯一元素的数量为 k 你需要做以下事情确保你的题解可以被通过 更改数组 nums 使 nums 的前 k 个元素包含唯一元素并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。返回 k 。 判题标准: 系统会用下面的代码来测试你的题解: int[] nums [...]; // 输入数组 int[] expectedNums [...]; // 长度正确的期望答案int k removeDuplicates(nums); // 调用assert k expectedNums.length; for (int i 0; i k; i) {assert nums[i] expectedNums[i]; } 如果所有断言都通过那么您的题解将被 通过。 示例 1输入nums [1,1,2] 输出2, nums [1,2,_] 解释函数应该返回新的长度 2 并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。 示例 2输入nums [0,0,1,1,1,2,2,3,3,4] 输出5, nums [0,1,2,3,4] 解释函数应该返回新的长度 5  并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。 提示 1 nums.length 3 * -  nums[i]  nums 已按 非严格递增 排列 解题思路 这道题要求在原地删除数组中重复出现的元素使得每个元素只出现一次且要求返回删除重复元素后数组的新长度。由于 nums 数组已经是按非严格递增排序的我们可以通过双指针法来解决这个问题。 双指针法: 快指针fast用于遍历整个数组。慢指针slow用于记录不重复元素的位置。 具体步骤如下 初始化 slow 指针指向数组的第一个元素fast 指针从第二个元素开始遍历数组。如果 nums[fast] 与 nums[slow] 不相等说明遇到了新的元素我们将 slow 向前移动一位并将 fast 指针指向的值复制到 slow 位置。不断重复上述过程直到快指针遍历完整个数组。 最终慢指针的值加1即 slow 1就是数组中不重复元素的数量也就是我们需要返回的长度。 复杂度分析 时间复杂度O(n)其中 n 是数组的长度。每个元素最多只被遍历一次。空间复杂度O(1)我们只用了常数级别的额外空间。 代码实现 package org.zyf.javabasic.letcode.jd150.stringarray;/*** program: zyfboot-javabasic* description: 删除有序数组中的重复项* author: zhangyanfeng* create: 2024-08-24 20:27**/ public class RemoveDuplicates {public int removeDuplicates(int[] nums) {// 如果数组为空直接返回0if (nums.length 0) {return 0;}// 初始化慢指针int slow 0;// 快指针从1开始遍历for (int fast 1; fast nums.length; fast) {// 如果当前元素和慢指针元素不同if (nums[fast] ! nums[slow]) {// 慢指针前移slow;// 将快指针的值赋给慢指针当前位置nums[slow] nums[fast];}}// 返回不重复元素的数量return slow 1;} }4.删除有序数组中的重复项 II中等 题目描述 给你一个有序数组 nums 请你 原地 删除重复出现的元素使得出现次数超过两次的元素只出现两次 返回删除后数组的新长度。 不要使用额外的数组空间你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。 说明 为什么返回数值是整数但输出的答案是数组呢 请注意输入数组是以「引用」方式传递的这意味着在函数里修改输入数组对于调用者是可见的。 你可以想象内部操作如下: // nums 是以“引用”方式传递的。也就是说不对实参做任何拷贝 int len removeDuplicates(nums);// 在函数里修改输入数组对于调用者是可见的。 // 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。 for (int i 0; i len; i) {print(nums[i]); }示例 1输入nums [1,1,1,2,2,3] 输出5, nums [1,1,2,2,3] 解释函数应返回新长度 length  5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3。 不需要考虑数组中超出新长度后面的元素。 示例 2输入nums [0,0,1,1,1,1,2,3,3] 输出7, nums [0,0,1,1,2,3,3] 解释函数应返回新长度 length  7, 并且原数组的前七个元素被修改为 0, 0, 1, 1, 2, 3, 3。不需要考虑数组中超出新长度后面的元素。 提示 1 nums.length 3 * -  nums[i]  nums 已按升序排列 解题思路 这道题要求我们在有序数组中原地删除重复出现次数超过两次的元素使每个元素最多保留两次并返回新的数组长度。 由于数组已经是有序的我们可以使用双指针法来解决这个问题类似于之前删除重复元素的题目。但这里的区别在于我们允许每个元素最多出现两次。 具体步骤如下 初始化指针使用 slow 指针来标记不重复元素的位置。使用 fast 指针来遍历数组。 遍历数组 从数组的第三个元素开始索引 2因为前两个元素无论如何都要保留。对于每一个 nums[fast]我们检查它是否大于 nums[slow - 2]。如果是则说明 nums[fast] 可以保留在数组中即它出现的次数不会超过两次。若满足条件将 nums[fast] 复制到 slow 位置然后 slow 向前移动。 返回结果最终slow 指针的值就是新数组的长度。 复杂度分析 时间复杂度O(n)其中 n 是数组的长度。我们只遍历了一次数组。空间复杂度O(1)我们只用了常数级别的额外空间。 代码实现 package org.zyf.javabasic.letcode.jd150.stringarray;/*** program: zyfboot-javabasic* description: RemoveDuplicates* author: zhangyanfeng* create: 2024-08-24 20:32**/ public class RemoveDuplicates2 {public int removeDuplicates(int[] nums) {// 如果数组长度小于等于2则不需要处理直接返回数组长度if (nums.length 2) {return nums.length;}// 初始化慢指针指向第二个元素int slow 2;// 快指针从第三个元素开始遍历for (int fast 2; fast nums.length; fast) {// 如果当前元素大于slow-2位置的元素说明它可以被保留if (nums[fast] nums[slow - 2]) {// 将fast指针的值赋给slow指针并将slow指针向前移动nums[slow] nums[fast];slow;}}// 最终slow的位置就是数组的有效长度return slow;} }5.多数元素简单 题目描述 给定一个大小为 n 的数组 nums 返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的并且给定的数组总是存在多数元素。 示例 1输入nums [3,2,3] 输出3 示例 2输入nums [2,2,1,1,1,2,2] 输出2 提示 n nums.length1 n 5 * 104-109 nums[i] 109 进阶尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。 解题思路 可见LeetCode 热题 100 回顾-CSDN博客中第97题。 或数学思维编程练习总结_编程中的数学思维-CSDN博客中第1题。 6.轮转数组 中等 题目描述 给定一个整数数组 nums将数组中的元素向右轮转 k 个位置其中 k 是非负数。 示例 1:输入: nums [1,2,3,4,5,6,7], k 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5,6] 向右轮转 2 步: [6,7,1,2,3,4,5] 向右轮转 3 步: [5,6,7,1,2,3,4] 示例 2:输入nums [-1,-100,3,99], k 2 输出[3,99,-1,-100] 解释: 向右轮转 1 步: [99,-1,-100,3] 向右轮转 2 步: [3,99,-1,-100] 提示 1 nums.length \(10^{5}\) -\(2^{31}\) nums[i] \(2^{31}\) - 1 0 k \(10^{5}\) 进阶 尽可能想出更多的解决方案至少有 三种 不同的方法可以解决这个问题。 你可以使用空间复杂度为 O(1) 的 原地 算法解决这个问题吗 解题思路 可见LeetCode 热题 100 回顾-CSDN博客中第15题。 7.买卖股票的最佳时机简单 题目描述 给定一个数组 prices 它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润返回 0 。 示例 1输入[7,1,5,3,6,4] 输出5 解释在第 2 天股票价格 1的时候买入在第 5 天股票价格 6的时候卖出最大利润 6-1 5 。 注意利润不能是 7-1 6, 因为卖出价格需要大于买入价格同时你不能在买入前卖出股票。 示例 2输入prices [7,6,4,3,1] 输出0 解释在这种情况下, 没有交易完成, 所以最大利润为 0。 提示 1 prices.length   0 prices[i]  解题思路 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第77题。 也可见动态规划相关高频笔试汇总_动态规划编程题-CSDN博客中的第18题。 8.买卖股票的最佳时机 II 中等 题目描述 给你一个整数数组 prices 其中 prices[i] 表示某支股票第 i 天的价格。 在每一天你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买然后在 同一天 出售。 返回 你能获得的 最大 利润 。 示例 1输入prices [7,1,5,3,6,4] 输出7 解释在第 2 天股票价格 1的时候买入在第 3 天股票价格 5的时候卖出, 这笔交易所能获得利润 5 - 1 4。 随后在第 4 天股票价格 3的时候买入在第 5 天股票价格 6的时候卖出, 这笔交易所能获得利润 6 - 3 3。 最大总利润为 4 3 7 。 示例 2输入prices [1,2,3,4,5] 输出4 解释在第 1 天股票价格 1的时候买入在第 5 天 股票价格 5的时候卖出, 这笔交易所能获得利润 5 - 1 4。 最大总利润为 4 。 示例 3输入prices [7,6,4,3,1] 输出0 解释在这种情况下, 交易无法获得正利润所以不参与交易可以获得最大利润最大利润为 0。 提示 1 prices.length 3 * 0 prices[i]   解题思路 要解决这个问题我们可以利用贪心算法的思想来实现最优解。具体来说我们只要在价格上升的每一天都买入并卖出股票这样可以确保获得最大的利润。假设我们在第 i 天买入股票并在第 i1 天卖出如果 prices[i1] prices[i]那么我们就赚取了 prices[i1] - prices[i] 的利润。 复杂度分析 时间复杂度O(n)其中 n 是数组 prices 的长度。我们只需遍历一次数组即可计算出最大利润。空间复杂度O(1)我们只使用了常数级别的额外空间。 代码实现 package org.zyf.javabasic.letcode.jd150.stringarray;/*** program: zyfboot-javabasic* description: 买卖股票的最佳时机 II* author: zhangyanfeng* create: 2024-08-25 08:54**/ public class MaxProfit {public int maxProfit(int[] prices) {// 初始化最大利润为0int maxProfit 0;// 遍历价格数组从第二天开始计算for (int i 1; i prices.length; i) {// 如果今天的价格比昨天的高计算利润if (prices[i] prices[i - 1]) {// 累加利润maxProfit prices[i] - prices[i - 1];}}// 返回计算的最大利润return maxProfit;}// 测试用例public static void main(String[] args) {MaxProfit solution new MaxProfit();// 示例1int[] prices1 {7, 1, 5, 3, 6, 4};System.out.println(最大利润: solution.maxProfit(prices1)); // 输出应为7// 示例2int[] prices2 {1, 2, 3, 4, 5};System.out.println(最大利润: solution.maxProfit(prices2)); // 输出应为4// 示例3int[] prices3 {7, 6, 4, 3, 1};System.out.println(最大利润: solution.maxProfit(prices3)); // 输出应为0} }9.跳跃游戏中等 题目描述 给你一个非负整数数组 nums 你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标如果可以返回 true 否则返回 false 。 示例 1输入nums [2,3,1,1,4] 输出true 解释可以先跳 1 步从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。 示例 2输入nums [3,2,1,0,4] 输出false 解释无论怎样总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 所以永远不可能到达最后一个下标。 提示 1 nums.length   0 nums[i] 10 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第78题。 10.跳跃游戏 II中等 题目描述 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说如果你在 nums[i] 处你可以跳转到任意 nums[i j] 处: 0 j nums[i]  i j n 返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]。 示例 1:输入: nums [2,3,1,1,4] 输出: 2 解释: 跳到最后一个位置的最小跳跃数是 2。   从下标为 0 跳到下标为 1 的位置跳 1 步然后跳 3 步到达数组的最后一个位置。 示例 2:输入: nums [2,3,0,1,4] 输出: 2 提示: 1 nums.length 104 0 nums[i] 1000 题目保证可以到达 nums[n-1] 具体可见LeetCode 热题 100 回顾-CSDN博客中的第79题。 11.H 指数中等 题目描述 给你一个整数数组 citations 其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 h 指数。 根据维基百科上 h 指数的定义h 代表“高引用次数” 一名科研人员的 h 指数 是指他她至少发表了 h 篇论文并且 至少 有 h 篇论文被引用次数大于等于 h 。如果 h 有多种可能的值h 指数 是其中最大的那个。 示例 1输入citations [3,0,6,1,5] 输出3 解释给定数组表示研究者总共有 5 篇论文每篇论文相应的被引用了 3, 0, 6, 1, 5 次。   由于研究者有 3 篇论文每篇 至少 被引用了 3 次其余两篇论文每篇被引用 不多于 3 次所以她的 h 指数是 3。 示例 2输入citations [1,3,1] 输出1 提示 n citations.length1 n 50000 citations[i] 1000 解题思路 要计算研究者的 h 指数我们可以利用排序和线性扫描来解决。h 指数的定义是某位研究者发表的论文中有至少 h 篇论文被引用了至少 h 次。如果 h 有多种可能值则选择最大的那个 h。解决思路 排序首先将论文的引用次数数组 citations 按照引用次数从大到小进行排序。线性扫描扫描排序后的数组并找到最大的 h使得至少有 h 篇论文的引用次数大于等于 h。 复杂度分析 时间复杂度排序的时间复杂度为 O(n log n)其中 n 是数组 citations 的长度。线性扫描的时间复杂度为 O(n)因此整体时间复杂度为 O(n log n)。空间复杂度我们只需要常数级别的额外空间因此空间复杂度为 O(1)。 代码实现 package org.zyf.javabasic.letcode.jd150.stringarray;import java.util.Arrays;/*** program: zyfboot-javabasic* description: H 指数* author: zhangyanfeng* create: 2024-08-25 09:13**/ public class HIndex {public int hIndex(int[] citations) {// 对数组进行从大到小的排序Arrays.sort(citations);int n citations.length;// 线性扫描排序后的数组寻找最大的 hfor (int i 0; i n; i) {int h n - i;// 如果 citations[i] h说明至少有 h 篇论文被引用了至少 h 次if (citations[i] h) {return h;}}// 如果未找到合适的 h返回 0return 0;}// 测试用例public static void main(String[] args) {HIndex solution new HIndex();// 示例1int[] citations1 {3, 0, 6, 1, 5};System.out.println(h指数: solution.hIndex(citations1)); // 输出应为3// 示例2int[] citations2 {1, 3, 1};System.out.println(h指数: solution.hIndex(citations2)); // 输出应为1}}12.O(1) 时间插入、删除和获取随机元素中等 题目描述 实现RandomizedSet 类 RandomizedSet() 初始化 RandomizedSet 对象bool insert(int val) 当元素 val 不存在时向集合中插入该项并返回 true 否则返回 false 。bool remove(int val) 当元素 val 存在时从集合中移除该项并返回 true 否则返回 false 。int getRandom() 随机返回现有集合中的一项测试用例保证调用此方法时集合中至少存在一个元素。每个元素应该有 相同的概率 被返回。 你必须实现类的所有函数并满足每个函数的 平均 时间复杂度为 O(1) 。 示例输入 [RandomizedSet, insert, remove, insert, getRandom, remove, insert, getRandom] [[], [1], [2], [2], [], [1], [2], []] 输出 [null, true, false, true, 2, true, false, 2] 解释 RandomizedSet randomizedSet new RandomizedSet(); randomizedSet.insert(1); // 向集合中插入 1 。返回 true 表示 1 被成功地插入。 randomizedSet.remove(2); // 返回 false 表示集合中不存在 2 。 randomizedSet.insert(2); // 向集合中插入 2 。返回 true 。集合现在包含 [1,2] 。 randomizedSet.getRandom(); // getRandom 应随机返回 1 或 2 。 randomizedSet.remove(1); // 从集合中移除 1 返回 true 。集合现在包含 [2] 。 randomizedSet.insert(2); // 2 已在集合中所以返回 false 。 randomizedSet.getRandom(); // 由于 2 是集合中唯一的数字getRandom 总是返回 2 。 提示 -  val   - 1最多调用 insert、remove 和 getRandom 函数 2 *  次在调用 getRandom 方法时数据结构中 至少存在一个 元素。 解题思路 要实现 RandomizedSet 类我们需要支持插入、删除和随机获取元素的操作并且所有操作的平均时间复杂度要求为 O(1)。为此我们可以利用以下两种数据结构 哈希表HashMap用于存储每个元素的值和它在数组中的索引位置。这样可以在 O(1) 时间内查找元素是否存在并删除元素。动态数组ArrayList用于保存当前集合中的所有元素。我们可以通过索引随机获取元素并且在删除操作时可以通过交换元素来保持 O(1) 时间的删除操作。 解决思路 插入操作如果元素不存在于集合中将其添加到数组的末尾并将该元素及其对应的索引位置存入哈希表中。如果元素已经存在则直接返回 false。 删除操作如果元素存在于集合中将其与数组的最后一个元素交换位置然后移除最后一个元素。同时更新哈希表中被交换的元素的位置并移除待删除元素的哈希表记录。如果元素不存在则直接返回 false。 随机获取元素直接从数组中随机选择一个索引并返回该索引对应的元素。 复杂度分析 插入操作平均时间复杂度为 O(1)因为哈希表的插入和查找操作都是 O(1)。删除操作平均时间复杂度为 O(1)因为通过交换最后一个元素可以保持删除的 O(1) 操作。随机获取元素时间复杂度为 O(1)因为从数组中随机选择一个元素是 O(1) 操作。 代码实现 package org.zyf.javabasic.letcode.jd150.stringarray;import java.util.*;/*** program: zyfboot-javabasic* description: O(1) 时间插入、删除和获取随机元素* author: zhangyanfeng* create: 2024-08-25 09:18**/ public class RandomizedSet {// 动态数组用于存储集合中的元素private ListInteger nums;// 哈希表用于存储每个元素对应在动态数组中的索引private MapInteger, Integer valToIndex;private Random rand;// 构造函数初始化动态数组和哈希表public RandomizedSet() {nums new ArrayList();valToIndex new HashMap();rand new Random();}// 插入操作public boolean insert(int val) {// 如果元素已存在返回falseif (valToIndex.containsKey(val)) {return false;}// 在数组末尾添加新元素并在哈希表中记录其索引nums.add(val);valToIndex.put(val, nums.size() - 1);return true;}// 删除操作public boolean remove(int val) {// 如果元素不存在返回falseif (!valToIndex.containsKey(val)) {return false;}// 获取待删除元素的索引int index valToIndex.get(val);// 将待删除元素与数组的最后一个元素交换位置int lastElement nums.get(nums.size() - 1);nums.set(index, lastElement);valToIndex.put(lastElement, index);// 删除数组的最后一个元素并移除哈希表中的记录nums.remove(nums.size() - 1);valToIndex.remove(val);return true;}// 随机获取元素操作public int getRandom() {// 从数组中随机选择一个元素并返回return nums.get(rand.nextInt(nums.size()));}// 测试用例public static void main(String[] args) {RandomizedSet randomizedSet new RandomizedSet();System.out.println(randomizedSet.insert(1)); // trueSystem.out.println(randomizedSet.remove(2)); // falseSystem.out.println(randomizedSet.insert(2)); // trueSystem.out.println(randomizedSet.getRandom()); // 1 or 2System.out.println(randomizedSet.remove(1)); // trueSystem.out.println(randomizedSet.insert(2)); // falseSystem.out.println(randomizedSet.getRandom()); // 2} }13.除自身以外数组的乘积中等 题目描述 给你一个整数数组 nums返回 数组 answer 其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在  32 位 整数范围内。 请 不要使用除法且在 O(n) 时间复杂度内完成此题。 示例 1:输入: nums [1,2,3,4] 输出: [24,12,8,6] 示例 2:输入: nums [-1,1,0,-3,3] 输出: [0,0,9,0,0] 提示 2 nums.length   -30 nums[i] 30 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在  32 位 整数范围内 进阶你可以在 O(1) 的额外空间复杂度内完成这个题目吗 出于对空间复杂度分析的目的输出数组 不被视为 额外空间。 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第16题。 也可见LeetCode 精选 75 回顾-CSDN博客中的第7题。 也可见数组知识及编程练习总结-CSDN博客中的第19题。 14.加油站中等 题目描述 在一条环路上有 n 个加油站其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车从第 i 个加油站开往第 i1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发开始时油箱为空。 给定两个整数数组 gas 和 cost 如果你可以按顺序绕环路行驶一周则返回出发时加油站的编号否则返回 -1 。如果存在解则 保证 它是 唯一 的。 示例 1:输入: gas [1,2,3,4,5], cost [3,4,5,1,2] 输出: 3 解释: 从 3 号加油站(索引为 3 处)出发可获得 4 升汽油。此时油箱有 0 4 4 升汽油 开往 4 号加油站此时油箱有 4 - 1 5 8 升汽油 开往 0 号加油站此时油箱有 8 - 2 1 7 升汽油 开往 1 号加油站此时油箱有 7 - 3 2 6 升汽油 开往 2 号加油站此时油箱有 6 - 4 3 5 升汽油 开往 3 号加油站你需要消耗 5 升汽油正好足够你返回到 3 号加油站。 因此3 可为起始索引。 示例 2:输入: gas [2,3,4], cost [3,4,3] 输出: -1 解释: 你不能从 0 号或 1 号加油站出发因为没有足够的汽油可以让你行驶到下一个加油站。 我们从 2 号加油站出发可以获得 4 升汽油。 此时油箱有 0 4 4 升汽油 开往 0 号加油站此时油箱有 4 - 3 2 3 升汽油 开往 1 号加油站此时油箱有 3 - 3 3 3 升汽油 你无法返回 2 号加油站因为返程需要消耗 4 升汽油但是你的油箱只有 3 升汽油。 因此无论怎样你都不可能绕环路行驶一周。 提示: gas.length ncost.length n1 n  0 gas[i], cost[i]   解题思路 目标是找到一个出发的加油站使得从这个加油站出发后能够顺利绕环路一圈最终回到这个加油站。如果无法做到返回 -1。关键点 如果 gas[i] - cost[i] 的总和小于 0则无论从哪个加油站出发汽车都不可能完成一圈因为汽油总量不够。如果某个加油站作为起点能够顺利完成一圈那么从这个加油站之前的任何一个加油站出发都不能完成一圈因为它们的剩余油量在到达这个加油站之前已经不足了。 解题思路 遍历两次一次遍历计算总油量和总消耗量。如果总油量小于总消耗量则返回 -1。选择起点从第一个加油站开始逐个加油站计算从该站出发后的剩余油量。如果在某个加油站剩余油量为负则将起点移动到下一个加油站并重新计算剩余油量。 复杂度分析 时间复杂度O(n)只需要遍历数组一次即可完成计算。空间复杂度O(1)仅使用了常数级别的额外空间。 代码实现 package org.zyf.javabasic.letcode.jd150.stringarray;/*** program: zyfboot-javabasic* description: 加油站* author: zhangyanfeng* create: 2024-08-25 09:44**/ public class CanCompleteCircuit {public int canCompleteCircuit(int[] gas, int[] cost) {int totalGas 0; // 总油量int totalCost 0; // 总消耗int start 0; // 起点int tank 0; // 当前油箱剩余油量// 遍历所有加油站for (int i 0; i gas.length; i) {totalGas gas[i];totalCost cost[i];tank gas[i] - cost[i];// 如果当前油量不足以到达下一个加油站if (tank 0) {// 将起点设为下一个加油站start i 1;// 重置油箱tank 0;}}// 判断是否可以绕环一圈return totalGas totalCost ? start : -1;}public static void main(String[] args) {CanCompleteCircuit solution new CanCompleteCircuit();// 测试用例 1int[] gas1 {1, 2, 3, 4, 5};int[] cost1 {3, 4, 5, 1, 2};System.out.println(solution.canCompleteCircuit(gas1, cost1)); // 输出: 3// 测试用例 2int[] gas2 {2, 3, 4};int[] cost2 {3, 4, 3};System.out.println(solution.canCompleteCircuit(gas2, cost2)); // 输出: -1} }15.发糖果困难 题目描述 n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。 你需要按照以下要求给这些孩子分发糖果 每个孩子至少分配到 1 个糖果。相邻两个孩子评分更高的孩子会获得更多的糖果。 请你给每个孩子分发糖果计算并返回需要准备的 最少糖果数目 。 示例 1输入ratings [1,0,2] 输出5 解释你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。 示例 2输入ratings [1,2,2] 输出4 解释你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。 第三个孩子只得到 1 颗糖果这满足题面中的两个条件。 提示 n ratings.length1 n 2 * 0 ratings[i] 2 *  解题思路 理解这道题的关键在于如何满足所有孩子的评分条件同时尽量减少分配的糖果数量。 双向扫描法: 左到右扫描: 首先从左到右遍历数组。我们可以假设如果一个孩子的评分高于前一个孩子那么这个孩子应该比前一个孩子多获得一颗糖果。这样我们可以确保在从左到右的方向上所有评分更高的孩子都比左边的孩子获得更多糖果。右到左扫描: 然后从右到左遍历数组。这个步骤与前一步类似但我们需要确保对于右侧的孩子如果评分高于左边的孩子糖果数量也要比左边的多。这个步骤是为了修正左到右扫描时可能未考虑到的情况。 糖果数量的确定:对于每个孩子最终分配的糖果数量是两次扫描中各自确定的糖果数量的最大值。即如果在左到右扫描时确定的糖果数量为 left[i]在右到左扫描时确定的糖果数量为 right[i]那么最终该孩子获得的糖果数量为 max(left[i], right[i])。 最终结果:将所有孩子的糖果数量累加得到最少需要的糖果数。 复杂度分析 时间复杂度: O(n)我们对数组进行了两次扫描。空间复杂度: O(n)我们使用了两个额外的数组来保存每个孩子从左到右和从右到左扫描时的糖果数量。 代码实现 package org.zyf.javabasic.letcode.jd150.stringarray;import java.util.Arrays;/*** program: zyfboot-javabasic* description: 分发糖果* author: zhangyanfeng* create: 2024-08-25 09:51**/ public class Candy {public int candy(int[] ratings) {int n ratings.length;int[] candies new int[n];// 每个孩子至少给一颗糖果Arrays.fill(candies, 1);// 从左到右遍历确保评分更高的孩子获得更多的糖果for (int i 1; i n; i) {if (ratings[i] ratings[i - 1]) {candies[i] candies[i - 1] 1;}}// 从右到左遍历确保评分更高的孩子获得更多的糖果for (int i n - 2; i 0; i--) {if (ratings[i] ratings[i 1]) {candies[i] Math.max(candies[i], candies[i 1] 1);}}// 计算糖果总数int totalCandies 0;for (int candy : candies) {totalCandies candy;}return totalCandies;} }16.接雨水 困难 题目描述 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图计算按此排列的柱子下雨之后能接多少雨水。 示例 1 输入height [0,1,0,2,1,0,1,3,2,1,2,1] 输出6 解释上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图在这种情况下可以接 6 个单位的雨水蓝色部分表示雨水。  示例 2输入height [4,2,0,3,2,5]   输出9 提示 n height.length 1 n 2 *  0 height[i]   解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第7题。 也可见栈知识及编程练习总结-CSDN博客中的第6题。 17.罗马数字转整数简单 题目描述 罗马数字包含以下七种字符: I V X LCD 和 M。 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如 罗马数字 2 写做 II 即为两个并列的 1 。12 写做 XII 即为 X  II 。 27 写做  XXVII, 即为 XX  V  II 。 通常情况下罗马数字中小的数字在大的数字的右边。但也存在特例例如 4 不写做 IIII而是 IV 。数字 1 在数字 5 的左边所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地数字 9 表为 IX。这个特殊的规则只适用于以下六种情况 I 可以放在 V (5) 和 X (10) 的左边来表示 4 和 9。X 可以放在 L (50) 和 C (100) 的左边来表示 40 和 90。 C 可以放在 D (500) 和 M (1000) 的左边来表示 400 和 900。 给定一个罗马数字将其转换成整数。 示例 1:输入: s III 输出: 3 示例 2:输入: s IV 输出: 4 示例 3:输入: s IX 输出: 9 示例 4:输入: s LVIII 输出: 58 解释: L 50, V 5, III 3. 示例 5:输入: s MCMXCIV 输出: 1994 解释: M 1000, CM 900, XC 90, IV 4. 提示 1 s.length 15s 仅含字符 (I, V, X, L, C, D, M)题目数据保证 s 是一个有效的罗马数字且表示整数在范围 [1, 3999] 内题目所给测试用例皆符合罗马数字书写规则不会出现跨位等情况。IL 和 IM 这样的例子并不符合题目要求49 应该写作 XLIX999 应该写作 CMXCIX 。关于罗马数字的详尽书写规则可以参考 罗马数字 - 百度百科。 解题思路 可见数学思维编程练习总结_编程中的数学思维-CSDN博客中的第10题。 18.整数转罗马数字中等 题目描述 七个不同的符号代表罗马数字其值如下 符号值I1V5X10L50C100D500M1000 罗马数字是通过添加从最高到最低的小数位值的转换而形成的。将小数位值转换为罗马数字有以下规则 如果该值不是以 4 或 9 开头请选择可以从输入中减去的最大值的符号将该符号附加到结果减去其值然后将其余部分转换为罗马数字。如果该值以 4 或 9 开头使用 减法形式表示从以下符号中减去一个符号例如 4 是 5 (V) 减 1 (I): IV 9 是 10 (X) 减 1 (I)IX。仅使用以下减法形式4 (IV)9 (IX)40 (XL)90 (XC)400 (CD) 和 900 (CM)。只有 10 的次方I, X, C, M最多可以连续附加 3 次以代表 10 的倍数。你不能多次附加 5 (V)50 (L) 或 500 (D)。如果需要将符号附加4次请使用 减法形式。 给定一个整数将其转换为罗马数字。 示例 1输入num 3749   输出 MMMDCCXLIX 解释 3000 MMM 由于 1000 (M) 1000 (M) 1000 (M)700 DCC 由于 500 (D) 100 (C) 100 (C)40 XL 由于 50 (L) 减 10 (X)9 IX 由于 10 (X) 减 1 (I) 注意49 不是 50 (L) 减 1 (I) 因为转换是基于小数位示例 2输入num 58   输出LVIII 解释 50 L8 VIII示例 3输入num 1994   输出MCMXCIV 解释 1000 M900 CM90 XC4 IV提示 1 num 3999 解题思路 要将一个整数转换为罗马数字我们需要按照罗马数字的规则逐步构建字符串。罗马数字是通过添加或减去特定的符号来表示不同的值步骤 准备罗马数字符号及其对应的值:建立两个数组一个存储罗马数字的符号另一个存储这些符号对应的整数值。数组按从大到小的顺序排列。 逐步匹配: 遍历这些值检查当前数字 num 是否大于或等于当前的值。如果是则减去该值并将对应的罗马符号添加到结果字符串中。如果 num 以 4 或 9 为开头直接匹配减法形式的罗马符号。 终止条件:当 num 减为 0 时所有的罗马符号都已经添加完毕。 复杂度分析 时间复杂度: O(1)。虽然我们在遍历不同的罗马符号但是由于罗马数字有固定的符号个数和最大数值所以整体的操作次数是有限的。因此时间复杂度为常数级别。空间复杂度: O(1)。使用了少量的额外空间来存储符号和结果字符串因此空间复杂度也为常数级别。 代码实现 package org.zyf.javabasic.letcode.jd150.stringarray;/*** program: zyfboot-javabasic* description: 整数转罗马数字* author: zhangyanfeng* create: 2024-08-25 10:05**/ public class IntegerToRoman {public String intToRoman(int num) {// 罗马数字符号和对应的值int[] values {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};String[] symbols {M, CM, D, CD, C, XC, L, XL, X, IX, V, IV, I};StringBuilder roman new StringBuilder();// 遍历所有符号值for (int i 0; i values.length; i) {// 每次找到最大值减少numwhile (num values[i]) {num - values[i];roman.append(symbols[i]); // 添加符号到结果}}return roman.toString();}public static void main(String[] args) {IntegerToRoman converter new IntegerToRoman();// 测试用例System.out.println(converter.intToRoman(3749)); // 输出: MMMDCCXLIXSystem.out.println(converter.intToRoman(58)); // 输出: LVIIISystem.out.println(converter.intToRoman(1994)); // 输出: MCMXCIV} }19.最后一个单词的长度简单 题目描述 给你一个字符串 s由若干单词组成单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。 单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。 示例 1输入s Hello World 输出5 解释最后一个单词是“World”长度为 5。 示例 2输入s fly me to the moon  输出4 解释最后一个单词是“moon”长度为 4。 示例 3输入s luffy is still joyboy 输出6 解释最后一个单词是长度为 6 的“joyboy”。 提示 1 s.length  s 仅有英文字母和空格   组成s 中至少存在一个单词 解题思路 要计算字符串中最后一个单词的长度可以从字符串末尾开始扫描找到最后一个单词并计算其长度。考虑到字符串中可能包含前导或尾随空格我们需要跳过这些空格来定位最后一个单词。 从后向前遍历: 从字符串的末尾开始跳过所有的尾随空格。一旦找到一个非空格字符开始计数直到遇到下一个空格或到达字符串的开头。计数器的值就是最后一个单词的长度。 终止条件:遍历到字符串的开始处时结束循环。 复杂度分析 时间复杂度: O(n)其中 n 是字符串的长度。我们只需从字符串末尾向前遍历一次。空间复杂度: O(1)只使用了常数级的额外空间。 代码实现 package org.zyf.javabasic.letcode.jd150.stringarray;/*** program: zyfboot-javabasic* description: 最后一个单词的长度* author: zhangyanfeng* create: 2024-08-25 10:09**/ public class LastWordLength {public int lengthOfLastWord(String s) {int length 0;int index s.length() - 1;// 从后向前跳过空格while (index 0 s.charAt(index) ) {index--;}// 计算最后一个单词的长度while (index 0 s.charAt(index) ! ) {length;index--;}return length;}public static void main(String[] args) {LastWordLength solution new LastWordLength();// 测试用例System.out.println(solution.lengthOfLastWord(Hello World)); // 输出: 5System.out.println(solution.lengthOfLastWord( fly me to the moon )); // 输出: 4System.out.println(solution.lengthOfLastWord(luffy is still joyboy)); // 输出: 6} }20.最长公共前缀简单 题目描述 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀返回空字符串 。 示例 1输入strs [flower,flow,flight] 输出fl 示例 2输入strs [dog,racecar,car] 输出 解释输入不存在公共前缀。 提示 1 strs.length 2000 strs[i].length 200strs[i] 仅由小写英文字母组成 解题思路 要查找字符串数组中的最长公共前缀可以使用常用且直观的解法即 纵向扫描法 从第一个字符开始依次检查每个字符串在相同位置的字符是否相同。如果在某个位置的字符不匹配或者到达了某个字符串的末尾就停止比较并返回当前找到的最长公共前缀。如果全部字符都匹配则继续检查下一列字符。 复杂度分析 时间复杂度: O(S)其中 S 是所有字符串中字符数量的总和。在最坏情况下需要检查每个字符。空间复杂度: O(1)只使用了常数级的额外空间。 代码实现 package org.zyf.javabasic.letcode.jd150.stringarray;/*** program: zyfboot-javabasic* description: 最长公共前缀* author: zhangyanfeng* create: 2024-08-25 10:15**/ public class LongestCommonPrefix {public String longestCommonPrefix(String[] strs) {if (strs null || strs.length 0) {return ;}// 从第一个字符串的第一个字符开始比较for (int i 0; i strs[0].length(); i) {char c strs[0].charAt(i);for (int j 1; j strs.length; j) {// 如果在当前位置字符不匹配或已到达其他字符串的末尾if (i strs[j].length() || strs[j].charAt(i) ! c) {return strs[0].substring(0, i);}}}// 如果第一个字符串本身是最长公共前缀return strs[0];}public static void main(String[] args) {LongestCommonPrefix solution new LongestCommonPrefix();// 测试用例System.out.println(solution.longestCommonPrefix(new String[]{flower, flow, flight})); // 输出: flSystem.out.println(solution.longestCommonPrefix(new String[]{dog, racecar, car})); // 输出: } }21.反转字符串中的单词中等 题目描述 给你一个字符串 s 请你反转字符串中 单词 的顺序。 单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。 返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。 注意输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中单词间应当仅用单个空格分隔且不包含任何额外的空格。 示例 1输入s the sky is blue 输出blue is sky the 示例 2输入s  hello world   输出world hello 解释反转后的字符串中不能存在前导空格和尾随空格。 示例 3输入s a good   example 输出example good a 解释如果两个单词间有多余的空格反转后的字符串需要将单词间的空格减少到仅有一个。 提示 1 s.length   s 包含英文大小写字母、数字和空格 s 中 至少存在一个 单词 进阶如果字符串在你使用的编程语言中是一种可变数据类型请尝试使用 O(1) 额外空间复杂度的 原地 解法。 解题思路 具体可见LeetCode 精选 75 回顾-CSDN博客中的第6题。 类似反转还有字符串高频编程笔试汇总_字符串专题复习-CSDN博客中的第22题。 22.Z 字形变换中等 题目描述 将一个给定字符串 s 根据给定的行数 numRows 以从上往下、从左到右进行 Z 字形排列。 比如输入字符串为 PAYPALISHIRING 行数为 3 时排列如下 P A H N A P L S I I G Y I R 之后你的输出需要从左往右逐行读取产生出一个新的字符串比如PAHNAPLSIIGYIR。 请你实现这个将字符串进行指定行数变换的函数 string convert(string s, int numRows);示例 1输入s PAYPALISHIRING, numRows 3 输出PAHNAPLSIIGYIR 示例 2输入s PAYPALISHIRING, numRows 4 输出PINALSIGYAHRPI 解释 P I N A L S I G Y A H R P I 示例 3输入s A, numRows 1 输出A 提示 1 s.length 1000s 由英文字母小写和大写、, 和 . 组成1 numRows 1000 解题思路 具体可见字符串高频编程笔试汇总_字符串专题复习-CSDN博客中的第23题。 23.找出字符串中第一个匹配项的下标 简单 题目描述 给你两个字符串 haystack 和 needle 请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标下标从 0 开始。如果 needle 不是 haystack 的一部分则返回  -1 。 示例 1输入haystack sadbutsad, needle sad 输出0 解释sad 在下标 0 和 6 处匹配。 第一个匹配项的下标是 0 所以返回 0 。 示例 2输入haystack leetcode, needle leeto 输出-1 解释leeto 没有在 leetcode 中出现所以返回 -1 。 提示 1 haystack.length, needle.length 104haystack 和 needle 仅由小写英文字符组成 解题思路 这个问题可以通过字符串匹配算法来解决。最直接的方法是使用暴力匹配法从 haystack 的每个字符开始逐一匹配 needle直到找到匹配项或遍历完 haystack。暴力匹配法 从 haystack 的第一个字符开始检查 needle 是否与 haystack 从当前字符开始的子串匹配。如果匹配成功则返回匹配的起始索引。如果遍历完 haystack 仍未找到匹配项则返回 -1。 复杂度分析 时间复杂度O((N-M1) * M)其中 N 是 haystack 的长度M 是 needle 的长度。最坏情况下需要对每个位置都进行 M 次比较。空间复杂度O(1)只需要常数级的额外空间。 代码实现 package org.zyf.javabasic.letcode.jd150.stringarray;/*** program: zyfboot-javabasic* description: 找出字符串中第一个匹配项的下标* author: zhangyanfeng* create: 2024-08-25 10:27**/ public class StrIndex {public int strStr(String haystack, String needle) {// 获取 haystack 和 needle 的长度int n haystack.length();int m needle.length();// 遍历 haystack 的每个位置尝试匹配 needlefor (int i 0; i n - m; i) {// 取出 haystack 中的子串String substring haystack.substring(i, i m);// 检查子串是否与 needle 相等if (substring.equals(needle)) {return i; // 如果匹配返回起始位置}}// 如果没有找到匹配返回 -1return -1;} }24.文本左右对齐困难 题目描述 给定一个单词数组 words 和一个长度 maxWidth 重新排版单词使其成为每行恰好有 maxWidth 个字符且左右两端对齐的文本。 你应该使用 “贪心算法” 来放置给定的单词也就是说尽可能多地往每行中放置单词。必要时可用空格   填充使得每行恰好有 maxWidth 个字符。 要求尽可能均匀分配单词间的空格数量。如果某一行单词间的空格不能均匀分配则左侧放置的空格数要多于右侧的空格数。 文本的最后一行应为左对齐且单词之间不插入额外的空格。 注意: 单词是指由非空格字符组成的字符序列。每个单词的长度大于 0小于等于 maxWidth。输入单词数组 words 至少包含一个单词。 示例 1:输入: words [This, is, an, example, of, text, justification.], maxWidth 16 输出: [    This    is    an,    example  of text,    justification.   ] 示例 2:输入:words [What,must,be,acknowledgment,shall,be], maxWidth 16 输出: [   What   must   be,   acknowledgment  ,   shall be         ] 解释: 注意最后一行的格式应为 shall be 而不是 shall be,   因为最后一行应为左对齐而不是左右两端对齐。 第二行同样为左对齐这是因为这行只包含一个单词。 示例 3:输入:words [Science,is,what,we,understand,well,enough,to,explain,to,a,computer.,Art,is,everything,else,we,do]maxWidth 20 输出: [   Science  is  what we, understand      well,   enough to explain to,   a  computer.  Art is,   everything  else  we,   do                   ] 提示: 1 words.length 3001 words[i].length 20words[i] 由小写英文字母和符号组成1 maxWidth 100words[i].length maxWidth 解题思路 贪心分配单词从单词数组中逐个取出单词并将它们放置在当前行中直到当前行无法再放置更多单词即放置当前单词后总长度超过 maxWidth。 计算行内空格 一旦确定了当前行应包含的所有单词我们需要计算如何在这些单词之间分配空格。对于每行文本除最后一行外其它行都需要左右对齐。计算行中单词之间的空格数。平均分配空格如果有多余的空格优先分配到靠左的空隙中。 格式化每行将计算得到的空格插入到单词之间形成需要的格式。对于最后一行单词左对齐剩余的空格填充在行尾。 复杂度分析 时间复杂度O(n)其中 n 是单词数组 words 的长度。每个单词只被处理一次因此整体时间复杂度是线性的。空间复杂度O(n)用于存储结果和处理每行的单词列表。 代码实现 package org.zyf.javabasic.letcode.jd150.stringarray;import java.util.ArrayList; import java.util.List;/*** program: zyfboot-javabasic* description: 文本左右对齐* author: zhangyanfeng* create: 2024-08-25 10:31**/ public class FullJustify {public ListString fullJustify(String[] words, int maxWidth) {ListString result new ArrayList();ListString currentLine new ArrayList();int numOfLetters 0;for (String word : words) {// 如果当前行可以容纳这个单词if (numOfLetters word.length() currentLine.size() maxWidth) {currentLine.add(word);numOfLetters word.length();} else {// 如果当前行满了处理当前行的文本result.add(justifyLine(currentLine, numOfLetters, maxWidth, false));currentLine new ArrayList();currentLine.add(word);numOfLetters word.length();}}// 处理最后一行左对齐result.add(justifyLine(currentLine, numOfLetters, maxWidth, true));return result;}private String justifyLine(ListString words, int numOfLetters, int maxWidth, boolean isLastLine) {// 如果是最后一行或只有一个单词左对齐if (isLastLine || words.size() 1) {StringBuilder sb new StringBuilder();for (String word : words) {sb.append(word).append( );}sb.deleteCharAt(sb.length() - 1); // 删除最后一个空格while (sb.length() maxWidth) {sb.append( );}return sb.toString();}// 计算每个空格的数量int totalSpaces maxWidth - numOfLetters;int spacesBetweenWords totalSpaces / (words.size() - 1);int extraSpaces totalSpaces % (words.size() - 1);StringBuilder sb new StringBuilder();for (int i 0; i words.size(); i) {sb.append(words.get(i));if (i words.size() - 1) {// 每个空格的基础数量for (int j 0; j spacesBetweenWords; j) {sb.append( );}// 分配多余的空格if (extraSpaces 0) {sb.append( );extraSpaces--;}}}return sb.toString();} }二、双指针 25.验证回文串简单 题目描述 如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。 字母和数字都属于字母数字字符。 给你一个字符串 s如果它是 回文串 返回 true 否则返回 false 。 示例 1输入: s A man, a plan, a canal: Panama 输出true 解释amanaplanacanalpanama 是回文串。 示例 2输入s race a car 输出false 解释raceacar 不是回文串。 示例 3输入s  输出true 解释在移除非字母数字字符之后s 是一个空字符串 。 由于空字符串正着反着读都一样所以是回文串。 提示 1 s.length 2 * s 仅由可打印的 ASCII 字符组成 解题思路 可见字符串高频编程笔试汇总_字符串专题复习-CSDN博客中第6题。 26.判断子序列简单 题目描述 给定字符串 s 和 t 判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些也可以不删除字符而不改变剩余字符相对位置形成的新字符串。例如ace是abcde的一个子序列而aec不是。 进阶 如果有大量输入的 S称作 S1, S2, ... , Sk 其中 k 10亿你需要依次检查它们是否为 T 的子序列。在这种情况下你会怎样改变代码 示例 1输入s abc, t ahbgdc 输出true 示例 2输入s axc, t ahbgdc 输出false 提示 0 s.length 1000 t.length 10^4两个字符串都只由小写字符组成。 解题思路 可见LeetCode 精选 75 回顾-CSDN博客中的第11题。 27.两数之和 II - 输入有序数组中等 题目描述 给你一个下标从 1 开始的整数数组 numbers 该数组已按 非递减顺序排列  请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] 则 1 index1 index2 numbers.length 。 以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。 你可以假设每个输入 只对应唯一的答案 而且你 不可以 重复使用相同的元素。 你所设计的解决方案必须只使用常量级的额外空间。 示例 1输入numbers [2,7,11,15], target 9 输出[1,2] 解释2 与 7 之和等于目标数 9 。因此 index1 1, index2 2 。返回 [1, 2] 。 示例 2输入numbers [2,3,4], target 6 输出[1,3] 解释2 与 4 之和等于目标数 6 。因此 index1 1, index2 3 。返回 [1, 3] 。 示例 3输入numbers [-1,0], target -1 输出[1,2] 解释-1 与 0 之和等于目标数 -1 。因此 index1 1, index2 2 。返回 [1, 2] 。 提示 2 numbers.length 3 * -1000 numbers[i] 1000numbers 按 非递减顺序 排列-1000 target 1000仅存在一个有效答案 解题思路 如果是无序数组的话可见数组知识及编程练习总结-CSDN博客中的第1题。 由于数组 numbers 是按非递减顺序排列的我们可以利用这一点来设计一个高效的解法。具体来说我们可以使用“双指针”方法来找到满足条件的两个数。这种方法的优势在于其时间复杂度为 O(n)同时只使用常量级的额外空间。双指针法的步骤 初始化指针使用两个指针 left 和 right。left 指针从数组的起始位置开始right 指针从数组的末尾位置开始。 遍历数组 计算 numbers[left] 和 numbers[right] 的和。如果和等于目标值 target返回这两个指针的位置加 1因为题目要求返回的是从 1 开始的下标。如果和小于 target说明我们需要更大的数因此将 left 指针向右移动一步。如果和大于 target说明我们需要更小的数因此将 right 指针向左移动一步。 终止条件当 left 指针不再小于 right 指针时算法结束。 这种方法利用了数组的有序性使得每一步都可以排除掉一些不必要的元素从而保证了时间复杂度为 O(n)。 复杂度分析 时间复杂度O(n)其中 n 是数组 numbers 的长度。每个元素最多只被访问一次因此整体时间复杂度是线性的。空间复杂度O(1)只使用了常量级的额外空间用于存储指针和变量。 代码实现 package org.zyf.javabasic.letcode.jd150.twopoints;/*** program: zyfboot-javabasic* description: 两数之和 II - 输入有序数组* author: zhangyanfeng* create: 2024-08-25 10:43**/ public class TwoSum {public int[] twoSum(int[] numbers, int target) {int left 0; // 左指针int right numbers.length - 1; // 右指针// 遍历直到两个指针重合while (left right) {int sum numbers[left] numbers[right]; // 计算当前指针指向的两个数的和if (sum target) {// 如果和等于目标值返回下标加 1因为题目要求从 1 开始return new int[] {left 1, right 1};} else if (sum target) {// 如果和小于目标值左指针向右移动left;} else {// 如果和大于目标值右指针向左移动right--;}}// 如果没有找到符合条件的两个数题目保证有唯一解这里只是为了编译器完整性return new int[] {-1, -1};} }28.盛最多水的容器中等 题目描述 给定一个长度为 n 的整数数组 height 。有 n 条垂线第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明你不能倾斜容器。 示例 1 输入[1,8,6,2,5,4,8,3,7] 输出49  解释图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下容器能够容纳水表示为蓝色部分的最大值为 49。 示例 2输入height [1,1] 输出1 提示 n height.length 2 n   0 height[i]   解题思路 可见LeetCode 精选 75 回顾-CSDN博客中的第12题。 也可见LeetCode 热题 100 回顾-CSDN博客中的第5题。 29.三数之和中等 题目描述 给你一个整数数组 nums 判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k 同时还满足 nums[i] nums[j] nums[k] 0 。请你返回所有和为 0 且不重复的三元组。 注意答案中不可以包含重复的三元组。 示例 1输入nums [-1,0,1,2,-1,-4] 输出[[-1,-1,2],[-1,0,1]] 解释 nums[0] nums[1] nums[2] (-1) 0 1 0 。 nums[1] nums[2] nums[4] 0 1 (-1) 0 。 nums[0] nums[3] nums[4] (-1) 2 (-1) 0 。 不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。 注意输出的顺序和三元组的顺序并不重要。 示例 2输入nums [0,1,1] 输出[] 解释唯一可能的三元组和不为 0 。 示例 3输入nums [0,0,0] 输出[[0,0,0]] 解释唯一可能的三元组和为 0 。 提示 3 nums.length 3000-  nums[i]   解题思路 可见LeetCode 热题 100 回顾-CSDN博客中的第6题。 也可见数组知识及编程练习总结-CSDN博客中的第2题。 三、滑动窗口 30.长度最小的子数组中等 题目描述 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的  子数组  [numsl, numsl1, ..., numsr-1, numsr] 并返回其长度。如果不存在符合条件的子数组返回 0 。 示例 1输入target 7, nums [2,3,1,2,4,3] 输出2 解释子数组 [4,3] 是该条件下的长度最小的子数组。 示例 2输入target 4, nums [1,4,4] 输出1 示例 3输入target 11, nums [1,1,1,1,1,1,1,1] 输出0 提示 1 target 1091 nums.length 1051 nums[i] 105 解题思路 为了找到满足总和大于等于 target 的最小长度子数组我们可以使用滑动窗口双指针的方法 初始化变量left 指针表示当前子数组的起始位置。right 指针表示当前子数组的结束位置。currentSum 用于记录当前子数组的总和。minLength 用于记录找到的最小长度初始值设为无穷大Integer.MAX_VALUE。 扩展窗口使用 right 指针从头到尾遍历数组同时将当前元素加入 currentSum。 收缩窗口每次 currentSum 大于等于 target 时更新 minLength 并将 left 指针向右移动以尝试缩小窗口同时更新 currentSum。 终止条件遍历结束后如果 minLength 仍为无穷大说明没有找到符合条件的子数组返回 0。 这种方法的时间复杂度是 O(n)因为每个元素最多被访问两次一次被 right 指针访问一次被 left 指针访问空间复杂度是 O(1)。 复杂度分析 时间复杂度O(n)其中 n 是数组 nums 的长度。每个元素最多被访问两次因此整体时间复杂度为线性。空间复杂度O(1)只使用了常量级的额外空间用于存储指针和变量。 代码实现 package org.zyf.javabasic.letcode.jd150.window;/*** program: zyfboot-javabasic* description: 长度最小的子数组* author: zhangyanfeng* create: 2024-08-25 10:58**/ public class MinSubArrayLen {public int minSubArrayLen(int target, int[] nums) {int left 0; // 左指针int currentSum 0; // 当前子数组的总和int minLength Integer.MAX_VALUE; // 记录最小长度// 遍历数组for (int right 0; right nums.length; right) {currentSum nums[right]; // 扩展窗口// 收缩窗口直到 currentSum 小于 targetwhile (currentSum target) {minLength Math.min(minLength, right - left 1); // 更新最小长度currentSum - nums[left]; // 收缩窗口}}// 如果 minLength 未更新说明没有找到符合条件的子数组return minLength Integer.MAX_VALUE ? 0 : minLength;} }31.无重复字符的最长子串中等 题目描述 给定一个字符串 s 请你找出其中不含有重复字符的 最长 子串 的长度。 示例 1:输入: s abcabcbb 输出: 3 解释: 因为无重复字符的最长子串是 abc所以其长度为 3。 示例 2:输入: s bbbbb 输出: 1 解释: 因为无重复字符的最长子串是 b所以其长度为 1。 示例 3:输入: s pwwkew 输出: 3 解释: 因为无重复字符的最长子串是 wke所以其长度为 3。   请注意你的答案必须是 子串 的长度pwke 是一个子序列不是子串。 提示 0 s.length 5 * 104s 由英文字母、数字、符号和空格组成 解题思路 可见LeetCode 热题 100 回顾-CSDN博客中的第8题。 可见字符串高频编程笔试汇总_字符串专题复习-CSDN博客中的第18题。 可见散列表相关知识及编程练习总结_散列函数 情景题-CSDN博客中的第1题。 32.串联所有单词的子串困难 题目描述 给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。  s 中的 串联子串 是指一个包含  words 中所有字符串以任意顺序排列连接起来的子串。 例如如果 words [ab,cd,ef] 那么 abcdef abefcdcdabef cdefabefabcd 和 efcdab 都是串联子串。 acdbef 不是串联子串因为他不是任何 words 排列的连接。 返回所有串联子串在 s 中的开始索引。你可以以 任意顺序 返回答案。 示例 1输入s barfoothefoobarman, words [foo,bar] 输出[0,9] 解释因为 words.length 2 同时 words[i].length 3连接的子字符串的长度必须为 6。 子串 barfoo 开始位置是 0。它是 words 中以 [bar,foo] 顺序排列的连接。 子串 foobar 开始位置是 9。它是 words 中以 [foo,bar] 顺序排列的连接。 输出顺序无关紧要。返回 [9,0] 也是可以的。 示例 2输入s wordgoodgoodgoodbestword, words [word,good,best,word] 输出[] 解释因为 words.length 4 并且 words[i].length 4所以串联子串的长度必须为 16。 s 中没有子串长度为 16 并且等于 words 的任何顺序排列的连接。 所以我们返回一个空数组。 示例 3输入s barfoofoobarthefoobarman, words [bar,foo,the] 输出[6,9,12] 解释因为 words.length 3 并且 words[i].length 3所以串联子串的长度必须为 9。 子串 foobarthe 开始位置是 6。它是 words 中以 [foo,bar,the] 顺序排列的连接。 子串 barthefoo 开始位置是 9。它是 words 中以 [bar,the,foo] 顺序排列的连接。 子串 thefoobar 开始位置是 12。它是 words 中以 [the,foo,bar] 顺序排列的连接。 提示 1 s.length 1041 words.length 50001 words[i].length 30words[i] 和 s 由小写英文字母组成 解题思路 给定的解法使用了滑动窗口和哈希表来解决寻找字符串中所有串联子串的起始位置的问题 初始化和基本参数设置m 是 words 数组中的单词数量。n 是每个单词的长度。ls 是字符串 s 的长度。遍历字符串 s 中的每一个可能的起始位置从 i 0 到 i n以保证覆盖所有可能的窗口起始位置。 预处理 对于每个起始位置 i检查从 i 开始的长度为 m * n 的子串是否是 words 中所有单词的排列。使用 differ 哈希表记录当前窗口的单词及其计数。初始化时将子串中的每个单词加入 differ 表中。 滑动窗口处理 将窗口向右滑动每次滑动一个单词的长度 n。更新 differ 哈希表加入新的单词并移除旧的单词。检查 differ 是否为空。如果为空说明当前窗口中的单词完全匹配了 words 中的单词记录当前的起始位置。 复杂度分析 时间复杂度 遍历每个可能的起始位置 i。由于窗口滑动的步长是 n所以 i 的最大范围是 n。对于每个起始位置滑动窗口遍历的次数是 O(ls / n)其中 ls 是字符串 s 的长度。更新哈希表操作的时间复杂度为 O(m)其中 m 是 words 中单词的数量。总的时间复杂度为 O(n * (ls / n) * m) O(ls * m)。 空间复杂度 differ 哈希表的大小最多为 m因为 differ 记录了 words 中的所有单词及其计数。总的空间复杂度为 O(m)。 代码实现 package org.zyf.javabasic.letcode.jd150.window;import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map;/*** program: zyfboot-javabasic* description: findSubstring* author: zhangyanfeng* create: 2024-08-25 11:05**/ public class FindSubstring {public ListInteger findSubstring(String s, String[] words) {ListInteger res new ArrayListInteger(); // 结果列表int m words.length; // 单词数量int n words[0].length(); // 每个单词的长度int ls s.length(); // 字符串 s 的长度// 遍历所有可能的起始位置for (int i 0; i n; i) {if (i m * n ls) {break; // 如果剩余长度不足以容纳所有单词则结束}// 记录当前窗口内的单词及其计数MapString, Integer differ new HashMapString, Integer();for (int j 0; j m; j) {String word s.substring(i j * n, i (j 1) * n); // 当前窗口中的单词differ.put(word, differ.getOrDefault(word, 0) 1); // 计数}// 从 words 中移除所有单词的计数准备验证for (String word : words) {differ.put(word, differ.getOrDefault(word, 0) - 1);if (differ.get(word) 0) {differ.remove(word); // 移除计数为零的单词}}// 滑动窗口for (int start i; start ls - m * n 1; start n) {if (start ! i) {// 更新窗口添加新单词移除旧单词String word s.substring(start (m - 1) * n, start m * n);differ.put(word, differ.getOrDefault(word, 0) 1);if (differ.get(word) 0) {differ.remove(word);}word s.substring(start - n, start);differ.put(word, differ.getOrDefault(word, 0) - 1);if (differ.get(word) 0) {differ.remove(word);}}// 检查是否所有单词都匹配if (differ.isEmpty()) {res.add(start); // 记录符合条件的起始位置}}}return res; // 返回结果} }33.最小覆盖子串困难 题目描述 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串则返回空字符串  。 注意 对于 t 中重复字符我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。如果 s 中存在这样的子串我们保证它是唯一的答案。 示例 1输入s ADOBECODEBANC, t ABC 输出BANC 解释最小覆盖子串 BANC 包含来自字符串 t 的 A、B 和 C。 示例 2输入s a, t a 输出a 解释整个字符串 s 是最小覆盖子串。 示例 3:输入: s a, t aa 输出:  解释: t 中两个字符 a 均应包含在 s 的子串中 因此没有符合条件的子字符串返回空字符串。 提示 m s.lengthn t.length1 m, n 105s 和 t 由英文字母组成 解题思路 可见LeetCode 热题 100 回顾-CSDN博客中的第12题。 可见字符串高频编程笔试汇总_字符串专题复习-CSDN博客中的第21题。 可见散列表相关知识及编程练习总结_散列函数 情景题-CSDN博客中的第3题。 四、矩阵 34.有效的数独中等 题目描述 请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 验证已经填入的数字是否有效即可。 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。请参考示例图 注意 一个有效的数独部分已被填充不一定是可解的。只需要根据以上规则验证已经填入的数字是否有效即可。空白格用 . 表示。 示例 1 输入board [[5,3,.,.,7,.,.,.,.] ,[6,.,.,1,9,5,.,.,.] ,[.,9,8,.,.,.,.,6,.] ,[8,.,.,.,6,.,.,.,3] ,[4,.,.,8,.,3,.,.,1] ,[7,.,.,.,2,.,.,.,6] ,[.,6,.,.,.,.,2,8,.] ,[.,.,.,4,1,9,.,.,5] ,[.,.,.,.,8,.,.,7,9]] 输出true示例 2 输入board [[8,3,.,.,7,.,.,.,.] ,[6,.,.,1,9,5,.,.,.] ,[.,9,8,.,.,.,.,6,.] ,[8,.,.,.,6,.,.,.,3] ,[4,.,.,8,.,3,.,.,1] ,[7,.,.,.,2,.,.,.,6] ,[.,6,.,.,.,.,2,8,.] ,[.,.,.,4,1,9,.,.,5] ,[.,.,.,.,8,.,.,7,9]] 输出false 解释除了第一行的第一个数字从 5 改为 8 以外空格内其他数字均与 示例1 相同。 但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。提示 board.length 9board[i].length 9board[i][j] 是一位数字1-9或者 . 解题思路 具体可见散列表相关知识及编程练习总结_散列函数 情景题-CSDN博客中的第2题。 35.螺旋矩阵中等 题目描述 给你一个 m 行 n 列的矩阵 matrix 请按照 顺时针螺旋顺序 返回矩阵中的所有元素。 示例 1 输入matrix [[1,2,3],[4,5,6],[7,8,9]] 输出[1,2,3,6,9,8,7,4,5]示例 2 输入matrix [[1,2,3,4],[5,6,7,8],[9,10,11,12]] 输出[1,2,3,4,8,12,11,10,9,5,6,7] 提示 m matrix.lengthn matrix[i].length1 m, n 10-100 matrix[i][j] 100 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第19题。 36.旋转图像中等 题目描述 给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在 原地 旋转图像这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 示例 1 输入matrix [[1,2,3],[4,5,6],[7,8,9]] 输出[[7,4,1],[8,5,2],[9,6,3]]示例 2 输入matrix [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]] 输出[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]提示 n matrix.length matrix[i].length1 n 20-1000 matrix[i][j] 1000 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第20题。 37.矩阵置零 中等 题目描述 给定一个 m x n 的矩阵如果一个元素为 0 则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 示例 1 输入matrix [[1,1,1],[1,0,1],[1,1,1]] 输出[[1,0,1],[0,0,0],[1,0,1]]示例 2 输入matrix [[0,1,2,0],[3,4,5,2],[1,3,1,5]] 输出[[0,0,0,0],[0,4,5,0],[0,3,1,0]]提示 m matrix.lengthn matrix[0].length1 m, n 200-  matrix[i][j]   - 1 进阶 一个直观的解决方案是使用  O(mn) 的额外空间但这并不是一个好的解决方案。一个简单的改进方案是使用 O(m  n) 的额外空间但这仍然不是最好的解决方案。你能想出一个仅使用常量空间的解决方案吗 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第18题。 38.生命游戏中等 题目描述 根据 百度百科  生命游戏 简称为 生命 是英国数学家约翰·何顿·康威在 1970 年发明的细胞自动机。 给定一个包含 m × n 个格子的面板每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态 1 即为 活细胞 live或 0 即为 死细胞 dead。每个细胞与其八个相邻位置水平垂直对角线的细胞都遵循以下四条生存定律 如果活细胞周围八个位置的活细胞数少于两个则该位置活细胞死亡如果活细胞周围八个位置有两个或三个活细胞则该位置活细胞仍然存活如果活细胞周围八个位置有超过三个活细胞则该位置活细胞死亡如果死细胞周围正好有三个活细胞则该位置死细胞复活 下一个状态是通过将上述规则同时应用于当前状态下的每个细胞所形成的其中细胞的出生和死亡是同时发生的。给你 m x n 网格面板 board 的当前状态返回下一个状态。 示例 1 输入board [[0,1,0],[0,0,1],[1,1,1],[0,0,0]] 输出[[0,0,0],[1,0,1],[0,1,1],[0,1,0]]示例 2 输入board [[1,1],[1,0]] 输出[[1,1],[1,1]] 提示 m board.lengthn board[i].length1 m, n 25board[i][j] 为 0 或 1 进阶 你可以使用原地算法解决本题吗请注意面板上所有格子需要同时被更新你不能先更新某些格子然后使用它们的更新后的值再更新其他格子。本题中我们使用二维数组来表示面板。原则上面板是无限的但当活细胞侵占了面板边界时会造成问题。你将如何解决这些问题 解题思路 链接https://leetcode.cn/problems/game-of-life/solutions/179750/sheng-ming-you-xi-by-leetcode-solution/ 先根据下面的图片理解题目中描述的细胞遵循的生存定律: 这个问题看起来很简单但有一个陷阱如果你直接根据规则更新原始数组那么就做不到题目中说的 同步 更新。假设你直接将更新后的细胞状态填入原始数组那么当前轮次其他细胞状态的更新就会引用到当前轮已更新细胞的状态但实际上每一轮更新需要依赖上一轮细胞的状态是不能用这一轮的细胞状态来更新的。 如上图所示已更新细胞的状态会影响到周围其他还未更新细胞状态的计算。一个最简单的解决方法就是复制一份原始数组复制的那一份永远不修改只作为更新规则的引用。这样原始数组的细胞值就不会被污染了。 算法 复制一份原始数组 根据复制数组中邻居细胞的状态来更新 board 中的细胞状态。 复杂度分析 时间复杂度O(mn)其中 m 和 n 分别为 board 的行数和列数。 空间复杂度O(mn)为复制数组占用的空间。 代码实现 package org.zyf.javabasic.letcode.jd150.window;/*** program: zyfboot-javabasic* description: gameOfLife* author: zhangyanfeng* create: 2024-08-25 11:50**/ public class GameOfLife {public void gameOfLife(int[][] board) {// 定义邻居位置的偏移量int[] neighbors {0, 1, -1};// 获取网格的行数和列数int rows board.length;int cols board[0].length;// 创建与原始网格大小相同的复制网格int[][] copyBoard new int[rows][cols];// 将原始网格的状态复制到复制网格中for (int row 0; row rows; row) {for (int col 0; col cols; col) {copyBoard[row][col] board[row][col];}}// 遍历每个细胞更新网格状态for (int row 0; row rows; row) {for (int col 0; col cols; col) {// 统计当前细胞的八个邻居中的活细胞数量int liveNeighbors 0;for (int i 0; i 3; i) {for (int j 0; j 3; j) {// 排除自己if (!(neighbors[i] 0 neighbors[j] 0)) {int r row neighbors[i];int c col neighbors[j];// 确保邻居在网格范围内并且是活细胞if (r 0 r rows c 0 c cols copyBoard[r][c] 1) {liveNeighbors;}}}}// 根据活邻居数和当前状态更新细胞状态if (copyBoard[row][col] 1) { // 当前细胞是活的if (liveNeighbors 2 || liveNeighbors 3) {board[row][col] 0; // 死亡}} else { // 当前细胞是死的if (liveNeighbors 3) {board[row][col] 1; // 复活}}}}} }五、哈希表 39.赎金信简单 题目描述 给你两个字符串ransomNote 和 magazine 判断 ransomNote 能不能由 magazine 里面的字符构成。 如果可以返回 true 否则返回 false 。 magazine 中的每个字符只能在 ransomNote 中使用一次。 示例 1输入ransomNote a, magazine b 输出false 示例 2输入ransomNote aa, magazine ab 输出false 示例 3输入ransomNote aa, magazine aab 输出true 提示 1 ransomNote.length, magazine.length 105ransomNote 和 magazine 由小写英文字母组成 解题思路 要解决这个问题我们需要判断 ransomNote 是否可以由 magazine 中的字符构成。每个字符在 magazine 中只能使用一次 统计字符频率使用两个频率数组或哈希表来统计 magazine 和 ransomNote 中每个字符的出现次数。 检查字符是否足够对于 ransomNote 中的每个字符检查在 magazine 中是否有足够的字符来满足需求。 复杂度分析 时间复杂度统计 magazine 和 ransomNote 中每个字符的频率都需要 O(n) 时间其中 n 是字符串的长度。因此总时间复杂度为 O(m n)其中 m 是 ransomNote 的长度n 是 magazine 的长度。 空间复杂度需要两个频率数组或哈希表来存储字符的出现次数。因为字符集有限26 个小写字母空间复杂度为 O(1)。 代码实现 package org.zyf.javabasic.letcode.jd150.hash;import java.util.HashMap; import java.util.Map;/*** program: zyfboot-javabasic* description: 赎金信* author: zhangyanfeng* create: 2024-08-25 11:56**/ public class CanConstruct {public boolean canConstruct(String ransomNote, String magazine) {// 使用 HashMap 统计 magazine 中每个字符的频率MapCharacter, Integer magazineCount new HashMap();// 遍历 magazine 统计每个字符的出现次数for (char c : magazine.toCharArray()) {magazineCount.put(c, magazineCount.getOrDefault(c, 0) 1);}// 遍历 ransomNote 检查是否有足够的字符for (char c : ransomNote.toCharArray()) {// 如果 magazine 中没有字符 c或字符 c 的数量不足if (!magazineCount.containsKey(c) || magazineCount.get(c) 0) {return false; // 无法构造 ransomNote}// 使用一个字符 c减少其在 magazine 中的频率magazineCount.put(c, magazineCount.get(c) - 1);}// 如果遍历完 ransomNote 中的所有字符后没有问题则返回 truereturn true;} }40.同构字符串简单 题目描述 给定两个字符串 s 和 t 判断它们是否是同构的。 如果 s 中的字符可以按某种映射关系替换得到 t 那么这两个字符串是同构的。 每个出现的字符都应当映射到另一个字符同时不改变字符的顺序。不同字符不能映射到同一个字符上相同字符只能映射到同一个字符上字符可以映射到自己本身。 示例 1:输入s  egg, t  add 输出true 示例 2输入s  foo, t  bar 输出false 示例 3输入s  paper, t  title 输出true 提示 1 s.length 5 * 104t.length s.lengths 和 t 由任意有效的 ASCII 字符组成 解题思路 具体可见散列表相关知识及编程练习总结_散列函数 情景题-CSDN博客中的第19题。 41.单词规律简单 题目描述 给定一种规律 pattern 和一个字符串 s 判断 s 是否遵循相同的规律。 这里的 遵循 指完全匹配例如 pattern 里的每个字母和字符串 s 中的每个非空单词之间存在着双向连接的对应规律。 示例1:输入: pattern  abba, s  dog cat cat dog 输出: true 示例 2:输入:pattern  abba, s  dog cat cat fish 输出: false 示例 3:输入: pattern  aaaa, s  dog cat cat dog 输出: false 提示: 1 pattern.length 300pattern 只包含小写英文字母1 s.length 3000s 只包含小写英文字母和  s 不包含 任何前导或尾随对空格s 中每个单词都被 单个空格 分隔 解题思路 具体可见散列表相关知识及编程练习总结_散列函数 情景题-CSDN博客中的第13题。 42.有效的字母异位词简单 题目描述 给定两个字符串 s 和 t 编写一个函数来判断 t 是否是 s 的字母异位词。 注意若 s 和 t 中每个字符出现的次数都相同则称 s 和 t 互为字母异位词。 示例 1:输入: s  anagram, t  nagaram 输出: true 示例 2:输入: s  rat, t  car 输出: false 提示: 1 s.length, t.length 5 * 104s 和 t 仅包含小写字母 进阶: 如果输入字符串包含 unicode 字符怎么办你能否调整你的解法来应对这种情况 解题思路 具体可见散列表相关知识及编程练习总结_散列函数 情景题-CSDN博客中的第5题。 43.字母异位词分组中等 题目描述 给你一个字符串数组请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。 字母异位词 是由重新排列源单词的所有字母得到的一个新单词。 示例 1:输入: strs  [eat, tea, tan, ate, nat, bat] 输出: [[bat],[nat,tan],[ate,eat,tea]] 示例 2输入: strs  [] 输出: [[]] 示例 3:输入: strs  [a] 输出: [[a]] 提示 1 strs.length 1040 strs[i].length 100strs[i] 仅包含小写字母 解题思路 具体可见散列表相关知识及编程练习总结_散列函数 情景题-CSDN博客中的第4题。 具体可见LeetCode 热题 100 回顾-CSDN博客中的第2题。 44.两数之和简单 题目描述 给定一个整数数组 nums 和一个整数目标值 target请你在该数组中找出 和为目标值 target  的那 两个 整数并返回它们的数组下标。 你可以假设每种输入只会对应一个答案并且你不能使用两次相同的元素。 你可以按任意顺序返回答案。 示例 1输入nums [2,7,11,15], target 9 输出[0,1] 解释因为 nums[0] nums[1] 9 返回 [0, 1] 。 示例 2输入nums [3,2,4], target 6 输出[1,2] 示例 3输入nums [3,3], target 6 输出[0,1] 提示 2 nums.length 104-109 nums[i] 109-109 target 109只会存在一个有效答案 进阶你可以想出一个时间复杂度小于 O(n2) 的算法吗 解题思路 具体可见数组知识及编程练习总结-CSDN博客中的第1题。 具体可见LeetCode 热题 100 回顾-CSDN博客中的第1题。 45.快乐数简单 题目描述 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为 对于一个正整数每一次将该数替换为它每个位置上的数字的平方和。然后重复这个过程直到这个数变为 1也可能是 无限循环 但始终变不到 1。如果这个过程 结果为 1那么这个数就是快乐数。 如果 n 是 快乐数 就返回 true 不是则返回 false 。 示例 1输入n 19 输出true 解释 12 92 82 82 22 68 62 82 100 12 02 02 1 示例 2输入n 2 输出false 提示 1 n 231 - 1 解题思路 具体可见数学思维编程练习总结_编程中的数学思维-CSDN博客中的第3题。 也可见散列表相关知识及编程练习总结_散列函数 情景题-CSDN博客中的第10题。 46.存在重复元素 II简单 题目描述 给你一个整数数组 nums 和一个整数 k 判断数组中是否存在两个 不同的索引 i 和 j 满足 nums[i] nums[j] 且 abs(i - j) k 。如果存在返回 true 否则返回 false 。 示例 1输入nums [1,2,3,1], k  3 输出true 示例 2输入nums [1,0,1,1], k  1 输出true 示例 3输入nums [1,2,3,1,2,3], k  2 输出false 提示 1 nums.length 105-109 nums[i] 1090 k 105 解题思路 也可见散列表相关知识及编程练习总结_散列函数 情景题-CSDN博客中的第12题。 47.最长连续序列中等 题目描述 给定一个未排序的整数数组 nums 找出数字连续的最长序列不要求序列元素在原数组中连续的长度。 请你设计并实现时间复杂度为 O(n) 的算法解决此问题。 示例 1输入nums [100,4,200,1,3,2] 输出4 解释最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。 示例 2输入nums [0,3,7,2,5,8,4,6,0,1] 输出9 提示 0 nums.length 105-109 nums[i] 109 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第3题。 六、区间 48.汇总区间简单 题目描述 给定一个  无重复元素 的 有序 整数数组 nums 。 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说nums 的每个元素都恰好被某个区间范围所覆盖并且不存在属于某个范围但不属于 nums 的数字 x 。 列表中的每个区间范围 [a,b] 应该按如下格式输出 a-b 如果 a ! ba 如果 a b 示例 1输入nums [0,1,2,4,5,7] 输出[0-2,4-5,7] 解释区间范围是 [0,2] -- 0-2 [4,5] -- 4-5 [7,7] -- 7 示例 2输入nums [0,2,3,4,6,8,9] 输出[0,2-4,6,8-9] 解释区间范围是 [0,0] -- 0 [2,4] -- 2-4 [6,6] -- 6 [8,9] -- 8-9 提示 0 nums.length 20-231 nums[i] 231 - 1nums 中的所有值都 互不相同nums 按升序排列 解题思路 为了找到最小的有序区间范围列表我们可以使用以下思路 遍历数组我们从数组的第一个元素开始逐个检查元素。确定区间对于每个元素检查它是否与下一个元素连续。如果是则继续扩展当前区间。如果不是则结束当前区间并开始新的区间。处理边界当我们处理到数组的末尾时要确保最后一个区间被正确处理。格式化区间对于每个区间根据开始和结束值的关系格式化为“a-b”或者“a”。 复杂度分析 时间复杂度O(n)其中 n 是数组 nums 的长度。我们只需遍历一次数组来构建区间。空间复杂度O(n)用于存储区间范围列表的结果。 代码实现 package org.zyf.javabasic.letcode.jd150.ranges;import java.util.ArrayList; import java.util.List;/*** program: zyfboot-javabasic* description: 汇总区间* author: zhangyanfeng* create: 2024-08-25 12:21**/ public class FindRanges {public ListString findRanges(int[] nums) {ListString result new ArrayList();if (nums.length 0) {return result; // 如果数组为空返回空列表}int start nums[0]; // 区间起始值int end nums[0]; // 区间结束值for (int i 1; i nums.length; i) {if (nums[i] end 1) {// 如果当前元素与结束值连续扩展当前区间end nums[i];} else {// 否则结束当前区间添加到结果中并开始新的区间result.add(formatRange(start, end));start nums[i];end nums[i];}}// 处理最后一个区间result.add(formatRange(start, end));return result;}private String formatRange(int start, int end) {// 格式化区间为字符串if (start end) {return String.valueOf(start);} else {return start - end;}}public static void main(String[] args) {FindRanges sol new FindRanges();int[] nums1 {0, 1, 2, 4, 5, 7};int[] nums2 {0, 2, 3, 4, 6, 8, 9};System.out.println(sol.findRanges(nums1)); // 输出: [0-2,4-5,7]System.out.println(sol.findRanges(nums2)); // 输出: [0,2-4,6,8-9]} }49.合并区间中等 题目描述 以数组 intervals 表示若干个区间的集合其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间并返回 一个不重叠的区间数组该数组需恰好覆盖输入中的所有区间 。 示例 1输入intervals [[1,3],[2,6],[8,10],[15,18]] 输出[[1,6],[8,10],[15,18]] 解释区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6]. 示例 2输入intervals [[1,4],[4,5]] 输出[[1,5]] 解释区间 [1,4] 和 [4,5] 可被视为重叠区间。 提示 1 intervals.length 104intervals[i].length 20 starti endi 104 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第14题。 具体可见数组知识及编程练习总结-CSDN博客中的第14题。 50.插入区间中等 题目描述 给你一个 无重叠的 按照区间起始端点排序的区间列表 intervals其中 intervals[i] [starti, endi] 表示第 i 个区间的开始和结束并且 intervals 按照 starti 升序排列。同样给定一个区间 newInterval [start, end] 表示另一个区间的开始和结束。 在 intervals 中插入区间 newInterval使得 intervals 依然按照 starti 升序排列且区间之间不重叠如果有必要的话可以合并区间。 返回插入之后的 intervals。 注意 你不需要原地修改 intervals。你可以创建一个新数组然后返回它。 示例 1输入intervals [[1,3],[6,9]], newInterval [2,5] 输出[[1,5],[6,9]] 示例 2输入intervals [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval [4,8] 输出[[1,2],[3,10],[12,16]] 解释这是因为新的区间 [4,8] 与 [3,5],[6,7],[8,10] 重叠。 提示 0 intervals.length 104intervals[i].length 20  starti  endi 105intervals 根据 starti 按 升序 排列newInterval.length 20  start  end 105 解题思路 初始化变量 result一个 Listint[] 用于存储最终的结果。i用于遍历 intervals 的指针。 处理新区间之前的区间遍历 intervals将所有在 newInterval 之前且不与 newInterval 重叠的区间直接添加到 result 中。 合并新区间对于与 newInterval 重叠的区间更新 newInterval 的范围以合并这些重叠区间。更新后的 newInterval 范围将包括所有与之重叠的区间。 处理新区间之后的区间将所有不与合并后的 newInterval 重叠的区间添加到 result 中。 转换结果格式将 Listint[] 转换为 int[][] 并返回。 复杂度分析 时间复杂度O(n)其中 n 是 intervals 的长度。我们只需遍历一次 intervals 来完成合并和插入操作。空间复杂度O(n)用于存储结果列表 result。 代码实现 package org.zyf.javabasic.letcode.jd150.ranges;import java.util.ArrayList; import java.util.List;/*** program: zyfboot-javabasic* description: 插入区间* author: zhangyanfeng* create: 2024-08-25 12:28**/ public class InsertInterval {public ListListInteger insertInterval(ListListInteger intervals, ListInteger newInterval) {ListListInteger result new ArrayList();int i 0;int n intervals.size();// 添加所有在 newInterval 之前且不重叠的区间while (i n intervals.get(i).get(1) newInterval.get(0)) {result.add(intervals.get(i));}// 合并所有与 newInterval 重叠的区间while (i n intervals.get(i).get(0) newInterval.get(1)) {newInterval.set(0, Math.min(newInterval.get(0), intervals.get(i).get(0)));newInterval.set(1, Math.max(newInterval.get(1), intervals.get(i).get(1)));i;}result.add(newInterval);// 添加所有在 newInterval 之后且不重叠的区间while (i n) {result.add(intervals.get(i));}return result;}public int[][] insert(int[][] intervals, int[] newInterval) {Listint[] result new ArrayList();int i 0;int n intervals.length;// 添加所有在 newInterval 之前且不重叠的区间while (i n intervals[i][1] newInterval[0]) {result.add(intervals[i]);}// 合并所有与 newInterval 重叠的区间while (i n intervals[i][0] newInterval[1]) {newInterval[0] Math.min(newInterval[0], intervals[i][0]);newInterval[1] Math.max(newInterval[1], intervals[i][1]);i;}result.add(newInterval);// 添加所有在 newInterval 之后且不重叠的区间while (i n) {result.add(intervals[i]);}// 将结果转换为 int[][] 并返回return result.toArray(new int[result.size()][]);}public static void main(String[] args) {InsertInterval sol new InsertInterval();int[][] intervals1 {{1, 3}, {6, 9}};int[] newInterval1 {2, 5};int[][] intervals2 {{1, 2}, {3, 5}, {6, 7}, {8, 10}, {12, 16}};int[] newInterval2 {4, 8};// 打印输出结果printResult(sol.insert(intervals1, newInterval1)); // 输出: [[1, 5], [6, 9]]printResult(sol.insert(intervals2, newInterval2)); // 输出: [[1, 2], [3, 10], [12, 16]]}private static void printResult(int[][] result) {for (int[] interval : result) {System.out.println([ interval[0] , interval[1] ]);}} }51.用最少数量的箭引爆气球中等 题目描述 有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points 其中points[i] [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。 一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭若有一个气球的直径的开始和结束坐标为 xstartxend 且满足  xstart ≤ x ≤ xend则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后可以无限地前进。 给你一个数组 points 返回引爆所有气球所必须射出的 最小 弓箭数 。 示例 1输入points [[10,16],[2,8],[1,6],[7,12]] 输出2 解释气球可以用2支箭来爆破: -在x 6处射出箭击破气球[2,8]和[1,6]。 -在x 11处发射箭击破气球[10,16]和[7,12]。 示例 2输入points [[1,2],[3,4],[5,6],[7,8]] 输出4 解释每个气球需要射出一支箭总共需要4支箭。 示例 3输入points [[1,2],[2,3],[3,4],[4,5]] 输出2 解释气球可以用2支箭来爆破: - 在x 2处发射箭击破气球[1,2]和[2,3]。 - 在x 4处射出箭击破气球[3,4]和[4,5]。 提示: 1 points.length 105points[i].length 2-231  xstart  xend  231 - 1 解题思路 具体可见LeetCode 精选 75 回顾-CSDN博客中的第73题。 七、栈 52.有效的括号简单 题目描述 给定一个只包括 (){}[] 的字符串 s 判断字符串是否有效。 有效字符串需满足 左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。每个右括号都有一个对应的相同类型的左括号。 示例 1输入s () 输出true 示例 2输入s ()[]{} 输出true 示例 3输入s (] 输出false 提示 1 s.length 104s 仅由括号 ()[]{} 组成 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第69题。 也可见栈知识及编程练习总结-CSDN博客中的第1题。 53.简化路径中等 题目描述 给你一个字符串 path 表示指向某一文件或目录的 Unix 风格 绝对路径 以 / 开头请你将其转化为更加简洁的规范路径。 在 Unix 风格的文件系统中一个点.表示当前目录本身此外两个点 .. 表示将目录切换到上一级指向父目录两者都可以是复杂相对路径的组成部分。任意多个连续的斜杠即//都被视为单个斜杠 / 。 对于此问题任何其他格式的点例如...均被视为文件/目录名称。 请注意返回的 规范路径 必须遵循下述格式 始终以斜杠 / 开头。两个目录名之间必须只有一个斜杠 / 。最后一个目录名如果存在不能 以 / 结尾。此外路径仅包含从根目录到目标文件或目录的路径上的目录即不含 . 或 ..。 返回简化后得到的 规范路径 。 示例 1输入path /home/  输出/home  解释应删除尾部斜杠。 示例 2输入path /home//foo/  输出/home/foo  解释多个连续的斜杠被单个斜杠替换。 示例 3输入path /home/user/Documents/../Pictures  输出/home/user/Pictures  解释 两个点 .. 表示上一级目录。 示例 4输入path /../  输出/  解释不可能从根目录上升级一级。 示例 5输入path /.../a/../b/c/../d/./  输出/.../b/d解释 ... 是此问题中目录的有效名称。 提示 1 path.length 3000path 由英文字母数字./ 或 _ 组成。path 是一个有效的 Unix 风格绝对路径。 解题思路 可见栈知识及编程练习总结-CSDN博客中的第9题。 54.最小栈中等 题目描述 设计一个支持 push pop top 操作并能在常数时间内检索到最小元素的栈。 实现 MinStack 类: MinStack() 初始化堆栈对象。void push(int val) 将元素val推入堆栈。void pop() 删除堆栈顶部的元素。int top() 获取堆栈顶部的元素。int getMin() 获取堆栈中的最小元素。 示例 1:输入 [MinStack,push,push,push,getMin,pop,top,getMin] [[],[-2],[0],[-3],[],[],[],[]] 输出 [null,null,null,null,-3,null,0,-2] 解释 MinStack minStack new MinStack(); minStack.push(-2); minStack.push(0); minStack.push(-3); minStack.getMin(); -- 返回 -3. minStack.pop(); minStack.top(); -- 返回 0. minStack.getMin(); -- 返回 -2. 提示 -231  val 231 - 1pop、top 和 getMin 操作总是在 非空栈 上调用push, pop, top, and getMin最多被调用 3 * 104 次 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第70题。 也可见栈知识及编程练习总结-CSDN博客中的第2题。 55.逆波兰表达式求值中等 题目描述 给你一个字符串数组 tokens 表示一个根据 逆波兰表示法 表示的算术表达式。 请你计算该表达式。返回一个表示表达式值的整数。 注意 有效的算符为 、-、* 和 / 。每个操作数运算对象都可以是一个整数或者另一个表达式。两个整数之间的除法总是 向零截断 。表达式中不含除零运算。输入是一个根据逆波兰表示法表示的算术表达式。答案及所有中间计算结果可以用 32 位 整数表示。 示例 1输入tokens [2,1,,3,*] 输出9 解释该算式转化为常见的中缀算术表达式为((2 1) * 3) 9 示例 2输入tokens [4,13,5,/,] 输出6 解释该算式转化为常见的中缀算术表达式为(4 (13 / 5)) 6 示例 3输入tokens [10,6,9,3,,-11,*,/,*,17,,5,] 输出22 解释该算式转化为常见的中缀算术表达式为 ((10 * (6 / ((9 3) * -11))) 17) 5 ((10 * (6 / (12 * -11))) 17) 5 ((10 * (6 / -132)) 17) 5 ((10 * 0) 17) 5 (0 17) 5 17 5 22 提示 1 tokens.length 104tokens[i] 是一个算符、-、* 或 /或是在范围 [-200, 200] 内的一个整数 逆波兰表达式 逆波兰表达式是一种后缀表达式所谓后缀就是指算符写在后面。 平常使用的算式则是一种中缀表达式如 ( 1 2 ) * ( 3 4 ) 。该算式的逆波兰表达式写法为 ( ( 1 2 ) ( 3 4 ) * ) 。 逆波兰表达式主要有以下两个优点 去掉括号后表达式无歧义上式即便写成 1 2 3 4 * 也可以依据次序计算出正确结果。适合用栈操作运算遇到数字则入栈遇到算符则取出栈顶两个数字进行计算并将结果压入栈中 解题思路 可见栈知识及编程练习总结-CSDN博客中的第7题。 56.基本计算器困难 题目描述 给你一个字符串表达式 s 请你实现一个基本计算器来计算并返回它的值。 注意:不允许使用任何将字符串作为数学表达式计算的内置函数比如 eval() 。  示例 1输入s 1 1 输出2 示例 2输入s 2-1 2  输出3 示例 3输入s (1(452)-3)(68) 输出23 提示 1 s.length 3 * 105s 由数字、、-、(、)、和   组成s 表示一个有效的表达式 不能用作一元运算(例如 1 和 (2 3) 无效)- 可以用作一元运算(即 -1 和 -(2 3) 是有效的)输入中不存在两个连续的操作符每个数字和运行的计算将适合于一个有符号的 32位 整数 解题思路 可见栈知识及编程练习总结-CSDN博客中的第8题。 八、链表 57.环形链表简单 题目描述 给你一个链表的头节点 head 判断链表中是否有环。 如果链表中有某个节点可以通过连续跟踪 next 指针再次到达则链表中存在环。 为了表示给定链表中的环评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置索引从 0 开始。注意pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。 如果链表中存在环 则返回 true 。 否则返回 false 。 示例 1 输入head [3,2,0,-4], pos 1 输出true 解释链表中有一个环其尾部连接到第二个节点。示例 2 输入head [1,2], pos 0 输出true 解释链表中有一个环其尾部连接到第一个节点。示例 3 输入head [1], pos -1 输出false 解释链表中没有环。提示 链表中节点的数目范围是 [0, 104]-105 Node.val 105pos 为 -1 或者链表中的一个 有效索引 。 进阶你能用 O(1)即常量内存解决此问题吗 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第25题。 也可见链表知识及编程练习总结中第2题。 58.两数相加中等 题目描述 给你两个 非空 的链表表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的并且每个节点只能存储 一位 数字。 请你将两个数相加并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外这两个数都不会以 0 开头。 示例 1 输入l1 [2,4,3], l2 [5,6,4] 输出[7,0,8] 解释342 465 807.示例 2输入l1 [0], l2 [0] 输出[0] 示例 3输入l1 [9,9,9,9,9,9,9], l2 [9,9,9,9] 输出[8,9,9,9,0,0,0,1] 提示 每个链表中的节点数在范围 [1, 100] 内0 Node.val 9题目数据保证列表表示的数字不含前导零 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第28题。 也可见链表知识及编程练习总结中第15题。 59.合并两个有序链表简单 题目描述 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。  示例 1 输入l1 [1,2,4], l2 [1,3,4] 输出[1,1,2,3,4,4]示例 2输入l1 [], l2 [] 输出[] 示例 3输入l1 [], l2 [0] 输出[0] 提示 两个链表的节点数目范围是 [0, 50]-100 Node.val 100l1 和 l2 均按 非递减顺序 排列 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第27题。 也可见链表知识及编程练习总结中第7题。 60.随机链表的复制中等 题目描述 给你一个长度为 n 的链表每个节点包含一个额外增加的随机指针 random 该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。 例如如果原链表中有 X 和 Y 两个节点其中 X.random -- Y 。那么在复制链表中对应的两个节点 x 和 y 同样有 x.random -- y 。 返回复制链表的头节点。 用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示 val一个表示 Node.val 的整数。random_index随机指针指向的节点索引范围从 0 到 n-1如果不指向任何节点则为  null 。 你的代码 只 接受原链表的头节点 head 作为传入参数 示例 1 输入head [[7,null],[13,0],[11,4],[10,2],[1,0]] 输出[[7,null],[13,0],[11,4],[10,2],[1,0]]示例 2 输入head [[1,1],[2,1]] 输出[[1,1],[2,1]]示例 3 输入head [[3,null],[3,0],[3,null]] 输出[[3,null],[3,0],[3,null]]提示 0 n 1000-104  Node.val 104Node.random 为 null 或指向链表中的节点。 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第32题。 61.反转链表 II中等 题目描述 给你单链表的头指针 head 和两个整数 left 和 right 其中 left right 。请你反转从位置 left 到位置 right 的链表节点返回 反转后的链表 。 示例 1 输入head [1,2,3,4,5], left 2, right 4 输出[1,4,3,2,5]示例 2输入head [5], left 1, right 1 输出[5] 提示 链表中节点数目为 n1 n 500-500 Node.val 5001 left right n 进阶 你可以使用一趟扫描完成反转吗 解题思路 可见链表知识及编程练习总结中第20题。 62.K 个一组翻转链表 困难 题目描述 给你链表的头节点 head 每 k 个节点一组进行翻转请你返回修改后的链表。 k 是一个正整数它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍那么请将最后剩余的节点保持原有顺序。 你不能只是单纯的改变节点内部的值而是需要实际进行节点交换。 示例 1 输入head [1,2,3,4,5], k 2 输出[2,1,4,3,5]示例 2 输入head [1,2,3,4,5], k 3 输出[3,2,1,4,5] 提示 链表中的节点数目为 n1 k n 50000 Node.val 1000 进阶你可以设计一个只用 O(1) 额外内存空间的算法解决此问题吗 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第31题。 也可见链表知识及编程练习总结中第10题。 63.删除链表的倒数第 N 个结点中等 题目描述 给你一个链表删除链表的倒数第 n 个结点并且返回链表的头结点。 示例 1 输入head [1,2,3,4,5], n 2 输出[1,2,3,5]示例 2输入head [1], n 1 输出[] 示例 3输入head [1,2], n 1 输出[1] 提示 链表中结点的数目为 sz1 sz 300 Node.val 1001 n sz 进阶你能尝试使用一趟扫描实现吗 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第29题。 也可见链表知识及编程练习总结中第4题。 64.删除排序链表中的重复元素 II中等 题目描述 给定一个已排序的链表的头 head  删除原始链表中所有重复数字的节点只留下不同的数字 。返回 已排序的链表 。 示例 1 输入head [1,2,3,3,4,4,5] 输出[1,2,5]示例 2 输入head [1,1,1,2,3] 输出[2,3]提示 链表中节点数目在范围 [0, 300] 内-100 Node.val 100题目数据保证链表已经按升序 排列 解题思路 可见链表知识及编程练习总结中第9题。 65.旋转链表中等 题目描述 给你一个链表的头节点 head 旋转链表将链表每个节点向右移动 k 个位置。 示例 1 输入head [1,2,3,4,5], k 2 输出[4,5,1,2,3]示例 2 输入head [0,1,2], k 4 输出[2,0,1]提示 链表中节点的数目在范围 [0, 500] 内-100 Node.val 1000 k 2 * 109 解题思路 可见链表知识及编程练习总结中第11题。 66.分隔链表中等 题目描述 给你一个链表的头节点 head 和一个特定值 x 请你对链表进行分隔使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。 你应当 保留 两个分区中每个节点的初始相对位置。 示例 1 输入head [1,4,3,2,5,2], x 3 输出[1,2,2,4,3,5]示例 2输入head [2,1], x 2 输出[1,2] 提示 链表中节点的数目在范围 [0, 200] 内-100 Node.val 100-200 x 200 解题思路 可见链表知识及编程练习总结中第12题。 67.LRU 缓存中等 题目描述 请你设计并实现一个满足  LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类 LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存int get(int key) 如果关键字 key 存在于缓存中则返回关键字的值否则返回 -1 。void put(int key, int value) 如果关键字 key 已经存在则变更其数据值 value 如果不存在则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity 则应该 逐出 最久未使用的关键字。 函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。 示例 输入 [LRUCache, put, put, get, put, get, put, get, get, get] [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]] 输出 [null, null, null, 1, null, -1, null, -1, 3, 4]解释 LRUCache lRUCache new LRUCache(2); lRUCache.put(1, 1); // 缓存是 {11} lRUCache.put(2, 2); // 缓存是 {11, 22} lRUCache.get(1); // 返回 1 lRUCache.put(3, 3); // 该操作会使得关键字 2 作废缓存是 {11, 33} lRUCache.get(2); // 返回 -1 (未找到) lRUCache.put(4, 4); // 该操作会使得关键字 1 作废缓存是 {44, 33} lRUCache.get(1); // 返回 -1 (未找到) lRUCache.get(3); // 返回 3 lRUCache.get(4); // 返回 4提示 1 capacity 30000 key 100000 value 105最多调用 2 * 105 次 get 和 put 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第35题。 也可见散列表相关知识及编程练习总结_散列函数 情景题-CSDN博客中第7题。 加强版可见聚焦新版综合编程能力面试考查汇总_关于编程有什么问题可以提问-CSDN博客中第7题。 九、二叉树 68.二叉树的最大深度简单 题目描述 给定一个二叉树 root 返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1 输入root [3,9,20,null,null,15,7] 输出3示例 2输入root [1,null,2] 输出2 提示 树中节点的数量在 [0, 104] 区间内。-100 Node.val 100 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第37题。 也可见树相关知识及编程练习总结_树编程-CSDN博客中第2题。 69.相同的树简单 题目描述 给你两棵二叉树的根节点 p 和 q 编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同并且节点具有相同的值则认为它们是相同的。 示例 1 输入p [1,2,3], q [1,2,3] 输出true示例 2 输入p [1,2], q [1,null,2] 输出false示例 3 输入p [1,2,1], q [1,1,2] 输出false提示 两棵树上的节点数目都在范围 [0, 100] 内-104 Node.val 104 解题思路 要判断两棵二叉树是否相同我们可以使用递归的方法逐节点进行比较  基础情况 如果两个节点都为 null则它们相同返回 true。如果其中一个节点为 null 而另一个不为 null则它们不同返回 false。如果两个节点的值不相同也返回 false。递归判断子树 递归比较左子树和右子树是否相同。如果左子树和右子树都相同则当前子树相同。 复杂度分析 时间复杂度每个节点在递归时只访问一次因此时间复杂度为 O(n)其中 n 是树中的节点数。 空间复杂度最坏情况下即树是链状的递归栈的深度为 O(n)所以空间复杂度也是 O(n)。 代码实现 package org.zyf.javabasic.letcode.jd150.tree;import org.zyf.javabasic.letcode.tree.base.TreeNode;/*** program: zyfboot-javabasic* description: 相同的树* author: zhangyanfeng* create: 2024-08-25 13:40**/ public class SameTree {public boolean isSameTree(TreeNode p, TreeNode q) {// 如果两个节点都为 null则两棵树相同if (p null q null) {return true;}// 如果其中一个节点为 null 或者两个节点的值不同则两棵树不相同if (p null || q null || p.val ! q.val) {return false;}// 递归判断左右子树是否相同return isSameTree(p.left, q.left) isSameTree(p.right, q.right);}public static void main(String[] args) {SameTree sol new SameTree();// 示例 1TreeNode p1 new TreeNode(1);p1.left new TreeNode(2);p1.right new TreeNode(3);TreeNode q1 new TreeNode(1);q1.left new TreeNode(2);q1.right new TreeNode(3);// 示例 2TreeNode p2 new TreeNode(1);p2.left new TreeNode(2);TreeNode q2 new TreeNode(1);q2.right new TreeNode(2);// 示例 3TreeNode p3 new TreeNode(1);p3.left new TreeNode(2);p3.right new TreeNode(1);TreeNode q3 new TreeNode(1);q3.left new TreeNode(1);q3.right new TreeNode(2);System.out.println(sol.isSameTree(p1, q1)); // 输出: trueSystem.out.println(sol.isSameTree(p2, q2)); // 输出: falseSystem.out.println(sol.isSameTree(p3, q3)); // 输出: false} }70.翻转二叉树简单 题目描述 给你一棵二叉树的根节点 root 翻转这棵二叉树并返回其根节点。 示例 1 输入root [4,2,7,1,3,6,9] 输出[4,7,2,9,6,3,1]示例 2 输入root [2,1,3] 输出[2,3,1]示例 3输入root [] 输出[] 提示 树中节点数目范围在 [0, 100] 内-100 Node.val 100 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第38题。 也可见树相关知识及编程练习总结_树编程-CSDN博客中第12题。 71.对称二叉树简单 题目描述 给你一个二叉树的根节点 root  检查它是否轴对称。 示例 1 输入root [1,2,2,3,4,4,3] 输出true示例 2 输入root [1,2,2,null,3,null,3] 输出false提示 树中节点数目在范围 [1, 1000] 内-100 Node.val 100 进阶你可以运用递归和迭代两种方法解决这个问题吗 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第39题。 也可见树相关知识及编程练习总结_树编程-CSDN博客中第4题。 72.从前序与中序遍历序列构造二叉树中等 题目描述 给定两个整数数组 preorder 和 inorder 其中 preorder 是二叉树的先序遍历 inorder 是同一棵树的中序遍历请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,7], inorder [9,3,15,20,7] 输出: [3,9,20,null,null,15,7]示例 2:输入: preorder [-1], inorder [-1] 输出: [-1] 提示: 1 preorder.length 3000inorder.length preorder.length-3000 preorder[i], inorder[i] 3000preorder 和 inorder 均 无重复 元素inorder 均出现在 preorderpreorder 保证 为二叉树的前序遍历序列inorder 保证 为二叉树的中序遍历序列 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第47题。 也可见树相关知识及编程练习总结_树编程-CSDN博客中第16题。 73. 从中序与后序遍历序列构造二叉树中等 题目描述 给定两个整数数组 inorder 和 postorder 其中 inorder 是二叉树的中序遍历 postorder 是同一棵树的后序遍历请你构造并返回这颗 二叉树 。 示例 1: 输入inorder [9,3,15,20,7], postorder [9,15,7,20,3] 输出[3,9,20,null,null,15,7]示例 2:输入inorder [-1], postorder [-1] 输出[-1] 提示: 1 inorder.length 3000postorder.length inorder.length-3000 inorder[i], postorder[i] 3000inorder 和 postorder 都由 不同 的值组成postorder 中每一个值都在 inorder 中inorder 保证是树的中序遍历postorder 保证是树的后序遍历 解题思路 要根据中序遍历inorder和后序遍历postorder构造二叉树可以利用后序遍历的特性后序遍历的最后一个元素是树的根节点。 从后序遍历中找到根节点后序遍历的最后一个元素是当前子树的根节点。 在中序遍历中定位根节点在中序遍历中找到根节点的位置这样可以将中序遍历划分为左子树和右子树。 递归构造左右子树 递归地在左子树的中序和后序遍历中构造左子树。递归地在右子树的中序和后序遍历中构造右子树。 构造并返回根节点将左右子树连接到根节点上返回构造的树。 复杂度分析 时间复杂度构建二叉树的时间复杂度为 O(n)其中 n 是树中的节点数。每个节点只处理一次。空间复杂度由于使用了递归空间复杂度取决于递归调用的深度即 O(h)其中 h 是树的高度。最坏情况下树是线性的此时空间复杂度为 O(n)。 代码实现 package org.zyf.javabasic.letcode.jd150.tree;import org.zyf.javabasic.letcode.tree.base.TreeNode;import java.util.HashMap; import java.util.Map;/*** program: zyfboot-javabasic* description: 从中序与后序遍历序列构造二叉树* author: zhangyanfeng* create: 2024-08-25 13:50**/ public class BuildTree {private MapInteger, Integer inorderIndexMap;public TreeNode buildTree(int[] inorder, int[] postorder) {int n inorder.length;// 创建一个哈希映射以存储中序遍历中每个值的索引inorderIndexMap new HashMap();for (int i 0; i n; i) {inorderIndexMap.put(inorder[i], i);}// 从后序遍历和中序遍历构建二叉树return buildTreeHelper(postorder, 0, n - 1, 0, n - 1);}private TreeNode buildTreeHelper(int[] postorder, int postStart, int postEnd, int inStart, int inEnd) {// 如果索引无效返回 nullif (postStart postEnd || inStart inEnd) {return null;}// 后序遍历的最后一个节点是当前子树的根节点int rootVal postorder[postEnd];TreeNode root new TreeNode(rootVal);// 在中序遍历中找到根节点的位置int inRootIndex inorderIndexMap.get(rootVal);int leftSubtreeSize inRootIndex - inStart;// 递归构建左子树root.left buildTreeHelper(postorder, postStart, postStart leftSubtreeSize - 1, inStart, inRootIndex - 1);// 递归构建右子树root.right buildTreeHelper(postorder, postStart leftSubtreeSize, postEnd - 1, inRootIndex 1, inEnd);return root;}public static void main(String[] args) {BuildTree sol new BuildTree();int[] inorder1 {9, 3, 15, 20, 7};int[] postorder1 {9, 15, 7, 20, 3};TreeNode root1 sol.buildTree(inorder1, postorder1);printPreorder(root1); // 输出前序遍历结果验证树的构造}private static void printPreorder(TreeNode node) {if (node ! null) {System.out.print(node.val );printPreorder(node.left);printPreorder(node.right);}} }74.填充每个节点的下一个右侧节点指针 II中等 题目描述 给定一个二叉树 struct Node {int val;Node *left;Node *right;Node *next; } 填充它的每个 next 指针让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点则将 next 指针设置为 NULL 。 初始状态下所有 next 指针都被设置为 NULL 。 示例 1 输入root [1,2,3,4,5,null,7] 输出[1,#,2,3,#,4,5,7,#] 解释给定二叉树如图 A 所示你的函数应该填充它的每个 next 指针以指向其下一个右侧节点如图 B 所示。序列化输出按层序遍历顺序由 next 指针连接# 表示每层的末尾。 示例 2输入root [] 输出[] 提示 树中的节点数在范围 [0, 6000] 内-100 Node.val 100 进阶 你只能使用常量级额外空间。使用递归解题也符合要求本题中递归程序的隐式栈空间不计入额外空间复杂度。 解题思路 采了逐层遍历二叉树的方法不使用额外的空间直接利用二叉树的 next 指针来遍历每一层。通过维护指向每一层最左侧节点的 start 指针遍历该层节点并同时建立下一层节点之间的 next 连接。具体步骤如下 初始化指针start指向当前层的最左节点。last指向当前层中已连接的最后一个节点。nextStart指向下一层最左节点。 逐层处理 对当前层的每个节点分别处理其左子节点和右子节点建立 next 连接。更新 nextStart以确保下一层遍历从该层的第一个节点开始。 复杂度分析 时间复杂度O(n)其中 n 为二叉树中的节点总数。每个节点仅访问一次。空间复杂度O(1)使用了常量级别的额外空间。 代码实现 package org.zyf.javabasic.letcode.jd150.tree;/*** program: zyfboot-javabasic* description: 填充每个节点的下一个右侧节点指针 II* author: zhangyanfeng* create: 2024-08-25 13:57**/ public class Connect {Node last null; // 当前层已连接的最后一个节点Node nextStart null; // 下一层最左侧的起始节点public Node connect(Node root) {if (root null) {return null;}Node start root; // 从根节点开始while (start ! null) {last null;nextStart null;for (Node p start; p ! null; p p.next) {if (p.left ! null) {handle(p.left); // 处理左子节点}if (p.right ! null) {handle(p.right); // 处理右子节点}}start nextStart; // 转向下一层的最左节点}return root;}// 处理每个节点连接next指针public void handle(Node p) {if (last ! null) {last.next p; // 将上一个节点的next指向当前节点}if (nextStart null) {nextStart p; // 记录下一层的起始节点}last p; // 更新last为当前节点}public static void main(String[] args) {Connect sol new Connect();// 示例测试Node root new Node(1);root.left new Node(2);root.right new Node(3);root.left.left new Node(4);root.left.right new Node(5);root.right.right new Node(7);sol.connect(root);printLevels(root);}// 辅助函数按层打印节点的 next 连接结果private static void printLevels(Node root) {Node levelStart root;while (levelStart ! null) {Node current levelStart;while (current ! null) {System.out.print(current.val );current current.next;}System.out.println(#);levelStart levelStart.left;}}static class Node {public int val;public Node left;public Node right;public Node next;public Node() {}public Node(int _val) {val _val;}public Node(int _val, Node _left, Node _right, Node _next) {val _val;left _left;right _right;next _next;}} }75.二叉树展开为链表中等 题目描述 给你二叉树的根结点 root 请你将它展开为一个单链表 展开后的单链表应该同样使用 TreeNode 其中 right 子指针指向链表中下一个结点而左子指针始终为 null 。展开后的单链表应该与二叉树 先序遍历 顺序相同。 示例 1 输入root [1,2,5,3,4,null,6] 输出[1,null,2,null,3,null,4,null,5,null,6]示例 2输入root [] 输出[] 示例 3输入root [0] 输出[0] 提示 树中结点数在范围 [0, 2000] 内-100 Node.val 100 进阶你可以使用原地算法O(1) 额外空间展开这棵树吗 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第46题。 76.路径总和简单 题目描述 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径这条路径上所有节点值相加等于目标和 targetSum 。如果存在返回 true 否则返回 false 。 叶子节点 是指没有子节点的节点。 示例 1 输入root [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum 22 输出true 解释等于目标和的根节点到叶节点路径如上图所示。示例 2 输入root [1,2,3], targetSum 5 输出false 解释树中存在两条根节点到叶子节点的路径 (1 -- 2): 和为 3 (1 -- 3): 和为 4 不存在 sum 5 的根节点到叶子节点的路径。 示例 3输入root [], targetSum 0 输出false 解释由于树是空的所以不存在根节点到叶子节点的路径。 提示 树中节点的数目在范围 [0, 5000] 内-1000 Node.val 1000-1000 targetSum 1000 解题思路 也可见树相关知识及编程练习总结_树编程-CSDN博客中第7题。 要判断二叉树中是否存在一条从根节点到叶子节点的路径使得路径上所有节点值的和等于给定的 targetSum可以采用递归或深度优先搜索DFS的方式。具体步骤 递归遍历从根节点开始递归遍历每个节点的左右子树同时将当前节点的值从 targetSum 中减去。叶子节点判断当到达叶子节点时检查剩余的 targetSum 是否等于该叶子节点的值。如果是则找到了符合条件的路径否则继续在左右子树中查找。边界条件如果节点为空则直接返回 false如果遍历到叶子节点并且 targetSum 恰好等于当前节点值返回 true。 复杂度分析 时间复杂度O(n)其中 n 是树中的节点数。最坏情况下需要遍历每个节点一次。空间复杂度O(h)其中 h 是树的高度。空间主要消耗在递归调用栈上最坏情况下例如完全不平衡树递归深度等于树的高度。 代码实现 package org.zyf.javabasic.letcode.jd150.tree;import org.zyf.javabasic.letcode.tree.base.TreeNode;/*** program: zyfboot-javabasic* description: 路径总和* author: zhangyanfeng* create: 2024-08-25 14:07**/ public class HasPathSum {public boolean hasPathSum(TreeNode root, int targetSum) {if (root null) {return false; // 如果当前节点为空直接返回 false}// 如果当前节点是叶子节点判断路径和是否等于 targetSumif (root.left null root.right null) {return root.val targetSum;}// 递归检查左右子树是否存在符合条件的路径int remainingSum targetSum - root.val;return hasPathSum(root.left, remainingSum) || hasPathSum(root.right, remainingSum);}public static void main(String[] args) {HasPathSum sol new HasPathSum();// 示例 1 测试TreeNode root1 new TreeNode(5);root1.left new TreeNode(4);root1.right new TreeNode(8);root1.left.left new TreeNode(11);root1.left.left.left new TreeNode(7);root1.left.left.right new TreeNode(2);root1.right.left new TreeNode(13);root1.right.right new TreeNode(4);root1.right.right.right new TreeNode(1);System.out.println(sol.hasPathSum(root1, 22)); // 输出: true// 示例 2 测试TreeNode root2 new TreeNode(1);root2.left new TreeNode(2);root2.right new TreeNode(3);System.out.println(sol.hasPathSum(root2, 5)); // 输出: false// 示例 3 测试TreeNode root3 null;System.out.println(sol.hasPathSum(root3, 0)); // 输出: false} }77.求根节点到叶节点数字之和中等 题目描述 给你一个二叉树的根节点 root 树中每个节点都存放有一个 0 到 9 之间的数字。 每条从根节点到叶节点的路径都代表一个数字 例如从根节点到叶节点的路径 1 - 2 - 3 表示数字 123 。 计算从根节点到叶节点生成的 所有数字之和 。 叶节点 是指没有子节点的节点。 示例 1 输入root [1,2,3] 输出25 解释从根到叶子节点路径 1-2 代表数字 12 从根到叶子节点路径 1-3 代表数字 13 因此数字总和 12 13 25 示例 2 输入root [4,9,0,5,1] 输出1026 解释 从根到叶子节点路径 4-9-5 代表数字 495. 从根到叶子节点路径 4-9-1 代表数字 491 从根到叶子节点路径 4-0 代表数字 40 因此数字总和 495 491 40 1026提示 树中节点的数目在范围 [1, 1000] 内0 Node.val 9树的深度不超过 10 解题思路 可见树相关知识及编程练习总结_树编程-CSDN博客中第18题。 78.二叉树中的最大路径和 困难 题目描述 二叉树中的 路径 被定义为一条节点序列序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点且不一定经过根节点。 路径和 是路径中各节点值的总和。 给你一个二叉树的根节点 root 返回其 最大路径和 。 示例 1 输入root [1,2,3] 输出6 解释最优路径是 2 - 1 - 3 路径和为 2 1 3 6 示例 2 输入root [-10,9,20,null,null,15,7] 输出42 解释最优路径是 15 - 20 - 7 路径和为 15 20 7 42提示 树中节点数目范围是 [1, 3 * 104]-1000 Node.val 1000 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第50题。 79.二叉搜索树迭代器中等 题目描述 实现一个二叉搜索树迭代器类BSTIterator 表示一个按中序遍历二叉搜索树BST的迭代器 BSTIterator(TreeNode root) 初始化 BSTIterator 类的一个对象。BST 的根节点 root 会作为构造函数的一部分给出。指针应初始化为一个不存在于 BST 中的数字且该数字小于 BST 中的任何元素。boolean hasNext() 如果向指针右侧遍历存在数字则返回 true 否则返回 false 。int next()将指针向右移动然后返回指针处的数字。 注意指针初始化为一个不存在于 BST 中的数字所以对 next() 的首次调用将返回 BST 中的最小元素。 你可以假设 next() 调用总是有效的也就是说当调用 next() 时BST 的中序遍历中至少存在一个下一个数字。 示例 输入 [BSTIterator, next, next, hasNext, next, hasNext, next, hasNext, next, hasNext] [[[7, 3, 15, null, null, 9, 20]], [], [], [], [], [], [], [], [], []] 输出 [null, 3, 7, true, 9, true, 15, true, 20, false] 解释 BSTIterator bSTIterator new BSTIterator([7, 3, 15, null, null, 9, 20]); bSTIterator.next(); // 返回 3 bSTIterator.next(); // 返回 7 bSTIterator.hasNext(); // 返回 True bSTIterator.next(); // 返回 9 bSTIterator.hasNext(); // 返回 True bSTIterator.next(); // 返回 15 bSTIterator.hasNext(); // 返回 True bSTIterator.next(); // 返回 20 bSTIterator.hasNext(); // 返回 False提示 树中节点的数目在范围 [1, 105] 内0 Node.val 106最多调用 105 次 hasNext 和 next 操作 进阶 你可以设计一个满足下述条件的解决方案吗next() 和 hasNext() 操作均摊时间复杂度为 O(1) 并使用 O(h) 内存。其中 h 是树的高度。 解题思路 二叉搜索树迭代器 (BSTIterator) 的目标是实现按中序遍历从小到大逐步返回二叉搜索树中的节点值。由于二叉搜索树BST的性质中序遍历即为有序的节点值排列。 要实现一个高效的迭代器考虑以下几点 存储结构可以使用栈来存储遍历路径上的节点确保能够按顺序访问。迭代过程每次调用 next() 时从栈中弹出一个节点并将该节点的右子树的所有左子节点压栈确保下次访问的是当前节点的后继。时间复杂度next() 和 hasNext() 操作的均摊时间复杂度应为 O(1)因为每个节点仅进栈和出栈一次。空间复杂度需要 O(h) 的空间来存储栈其中 h 是树的高度。 复杂度分析 时间复杂度 next(): O(1) 均摊时间复杂度。hasNext(): O(1)。空间复杂度O(h)其中 h 是树的高度。 代码实现 package org.zyf.javabasic.letcode.jd150.tree;import org.zyf.javabasic.letcode.tree.base.TreeNode;import java.util.Stack;/*** program: zyfboot-javabasic* description: 二叉搜索树迭代器* author: zhangyanfeng* create: 2024-08-25 14:24**/ public class BSTIterator {private StackTreeNode stack;// 初始化迭代器压入左侧路径上的所有节点public BSTIterator(TreeNode root) {stack new Stack();pushLeftBranch(root);}// 检查是否有下一个节点public boolean hasNext() {return !stack.isEmpty();}// 返回下一个最小的节点值public int next() {TreeNode node stack.pop(); // 弹出栈顶节点int result node.val;pushLeftBranch(node.right); // 如果有右子树将右子树的左侧路径压入栈中return result;}// 将从当前节点开始的左侧路径上的所有节点压栈private void pushLeftBranch(TreeNode node) {while (node ! null) {stack.push(node); // 压入当前节点node node.left; // 向左移动}} }80.完全二叉树的节点个数简单 题目描述 给你一棵 完全二叉树 的根节点 root 求出该树的节点个数。 完全二叉树 的定义如下在完全二叉树中除了最底层节点可能没填满外其余每层节点数都达到最大值并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层则该层包含 1~ 2h 个节点。 示例 1 输入root [1,2,3,4,5,6] 输出6示例 2输入root [] 输出0 示例 3输入root [1] 输出1 提示 树中节点的数目范围是[0, 5 * 104]0 Node.val 5 * 104题目数据保证输入的树是 完全二叉树 进阶遍历树来统计节点是一种时间复杂度为 O(n) 的简单解决方案。你可以设计一个更快的算法吗 解题思路 完全二叉树的定义是除了最后一层之外其他层都是满的。如果我们能够快速计算某个子树的高度并且通过这种高度来判断该子树是否是完全的那么我们就可以快速地计算出该子树的节点数量。 计算高度 左子树的高度leftHeight从根节点沿左子树路径一直走到叶子节点。右子树的高度rightHeight从根节点沿右子树路径一直走到叶子节点。 判断完全性 如果leftHeight rightHeight说明这个树是满的二叉树高度为h节点总数为2^h - 1。如果leftHeight ! rightHeight说明这个树的最后一层没有填满此时我们需要递归地计算左右子树的节点数。 递归 如果是满的子树直接返回节点数。否则递归地对左右子树求解最终得到整个树的节点总数。 复杂度分析 由于高度计算是O(log n)而我们每次递归会减少一层所以整体时间复杂度为O(log n * log n)。 代码实现 package org.zyf.javabasic.letcode.jd150.tree;import org.zyf.javabasic.letcode.tree.base.TreeNode;/*** program: zyfboot-javabasic* description: 完全二叉树的节点个数* author: zhangyanfeng* create: 2024-08-25 14:29**/ public class CountNodes {public int countNodes(TreeNode root) {// 如果根节点为空直接返回0if (root null) {return 0;}// 计算左子树高度int leftHeight getHeight(root.left);// 计算右子树高度int rightHeight getHeight(root.right);// 如果左子树高度等于右子树高度说明左子树是满的if (leftHeight rightHeight) {// 直接返回左子树节点数 右子树递归节点数 根节点1return (1 leftHeight) countNodes(root.right);} else {// 否则右子树是满的// 直接返回右子树节点数 左子树递归节点数 根节点1return (1 rightHeight) countNodes(root.left);}}// 辅助函数计算树的高度private int getHeight(TreeNode root) {int height 0;while (root ! null) {height;root root.left;}return height;} }81.二叉树的最近公共祖先中等 题目描述 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为“对于有根树 T 的两个节点 p、q最近公共祖先表示为一个节点 x满足 x 是 p、q 的祖先且 x 的深度尽可能大一个节点也可以是它自己的祖先。” 示例 1 输入root [3,5,1,6,2,0,8,null,null,7,4], p 5, q 1 输出3 解释节点 5 和节点 1 的最近公共祖先是节点 3 。 示例 2 输入root [3,5,1,6,2,0,8,null,null,7,4], p 5, q 4 输出5 解释节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。示例 3输入root [1,2], p 1, q 2 输出1 提示 树中节点数目在范围 [2, 105] 内。-109 Node.val 109所有 Node.val 互不相同 。p ! qp 和 q 均存在于给定的二叉树中。 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第49题。 也可见树相关知识及编程练习总结_树编程-CSDN博客中第5题。 十、二叉树层次遍历 82.二叉树的右视图中等 题目描述 给定一个二叉树的 根节点 root想象自己站在它的右侧按照从顶部到底部的顺序返回从右侧所能看到的节点值。 示例 1: 输入: [1,2,3,null,5,null,4] 输出: [1,3,4]示例 2:输入: [1,null,3] 输出: [1,3] 示例 3:输入: [] 输出: [] 提示: 二叉树的节点个数的范围是 [0,100]-100  Node.val 100  解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第45题。 也可见LeetCode 精选 75 回顾-CSDN博客中的第39题。 也可见树相关知识及编程练习总结_树编程-CSDN博客中第5题。 83.二叉树的层平均值简单 题目描述 给定一个非空二叉树的根节点 root , 以数组的形式返回每一层节点的平均值。与实际答案相差 10-5 以内的答案可以被接受。 示例 1 输入root [3,9,20,null,null,15,7] 输出[3.00000,14.50000,11.00000] 解释第 0 层的平均值为 3,第 1 层的平均值为 14.5,第 2 层的平均值为 11 。 因此返回 [3, 14.5, 11] 。示例 2: 输入root [3,9,20,15,7] 输出[3.00000,14.50000,11.00000]提示 树中节点数量在 [1, 104] 范围内-231  Node.val 231 - 1 解题思路 可见树相关知识及编程练习总结_树编程-CSDN博客中第11题。 84.二叉树的层序遍历中等 题目描述 给你二叉树的根节点 root 返回其节点值的 层序遍历 。 即逐层地从左到右访问所有节点。 示例 1 输入root [3,9,20,null,null,15,7] 输出[[3],[9,20],[15,7]]示例 2输入root [1] 输出[[1]] 示例 3输入root [] 输出[] 提示 树中节点数目在范围 [0, 2000] 内-1000 Node.val 1000 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第41题。 也可见树相关知识及编程练习总结_树编程-CSDN博客中第1题。 85.二叉树的锯齿形层序遍历中等 题目描述 给你二叉树的根节点 root 返回其节点值的 锯齿形层序遍历 。即先从左往右再从右往左进行下一层遍历以此类推层与层之间交替进行。 示例 1 输入root [3,9,20,null,null,15,7] 输出[[3],[20,9],[15,7]]示例 2输入root [1] 输出[[1]] 示例 3输入root [] 输出[] 提示 树中节点数目在范围 [0, 2000] 内-100 Node.val 100 解题思路 锯齿形层序遍历Zigzag Level Order Traversal是二叉树的广度优先搜索BFS的变形。我们可以用一个队列来实现层序遍历然后通过一个标志位来控制节点的访问顺序是从左到右还是从右到左。具体步骤如下 使用队列进行层序遍历将根节点加入队列。初始化一个布尔变量 leftToRight 表示当前层是否从左往右遍历。对于每一层先获取当前队列的大小 levelSize表示这一层的节点数。初始化一个双端队列 DequeInteger level 来存储当前层的结果。如果是从左到右遍历则直接在 level 的尾部添加节点值如果是从右到左遍历则在 level 的头部添加节点值。将当前层的节点值添加到 result 列表中并切换 leftToRight 的值。重复上述步骤直到队列为空。 复杂度分析 时间复杂度: O(n)其中 n 是二叉树的节点数。我们需要遍历每个节点一次。空间复杂度: O(n)最坏情况下队列中需要存储 n/2 个节点。 代码实现 package org.zyf.javabasic.letcode.jd150.tree;import org.zyf.javabasic.letcode.tree.base.TreeNode;import java.util.*;/*** program: zyfboot-javabasic* description: 二叉树的锯齿形层序遍历* author: zhangyanfeng* create: 2024-08-25 14:44**/ public class ZigzagLevelOrder {public ListListInteger zigzagLevelOrder(TreeNode root) {// 定义结果列表ListListInteger result new ArrayList();// 如果树为空直接返回空列表if (root null) {return result;}// 初始化队列进行层序遍历QueueTreeNode queue new LinkedList();queue.offer(root);// 标志位初始为从左到右boolean leftToRight true;while (!queue.isEmpty()) {// 当前层的节点数量int levelSize queue.size();// 使用双端队列来存储当前层的节点值DequeInteger level new LinkedList();for (int i 0; i levelSize; i) {TreeNode node queue.poll();if (leftToRight) {// 从左到右将节点值添加到队列的尾部level.offerLast(node.val);} else {// 从右到左将节点值添加到队列的头部level.offerFirst(node.val);}// 将当前节点的左右子节点加入队列if (node.left ! null) {queue.offer(node.left);}if (node.right ! null) {queue.offer(node.right);}}// 将当前层的结果添加到结果列表result.add(new LinkedList(level));// 切换遍历方向leftToRight !leftToRight;}return result;} }十一、二叉搜索树 86.二叉搜索树的最小绝对差简单 题目描述 给你一个二叉搜索树的根节点 root 返回 树中任意两不同节点值之间的最小差值 。 差值是一个正数其数值等于两值之差的绝对值。 示例 1 输入root [4,2,6,1,3] 输出1示例 2 输入root [1,0,48,null,null,12,49] 输出1提示 树中节点的数目范围是 [2, 104]0 Node.val 105 注意本题与 783 . - 力扣LeetCode 相同 解题思路 二叉搜索树BST具有一个重要性质对于任意节点其左子树的所有节点值都小于该节点值右子树的所有节点值都大于该节点值。因此BST 的中序遍历结果是一个递增的有序序列。 基于此性质求任意两不同节点值之间的最小差值可以通过以下步骤完成 中序遍历对 BST 进行中序遍历得到一个有序的节点值列表。计算最小差值在有序列表中任意两个相邻元素的差值是可能的最小差值。遍历这个列表计算所有相邻元素的差值并记录其中的最小值。 复杂度分析 时间复杂度: O(n)其中 n 是树中的节点数。中序遍历需要访问每个节点一次后续的最小差值计算也是 O(n)。空间复杂度: O(n)用于存储中序遍历的节点值列表。在最优的情况下可以使用 O(h) 的空间其中 h 是树的高度。 代码实现 package org.zyf.javabasic.letcode.jd150.tree;import org.zyf.javabasic.letcode.tree.base.TreeNode;/*** program: zyfboot-javabasic* description: 二叉搜索树的最小绝对差* author: zhangyanfeng* create: 2024-08-25 14:50**/ public class MinDiffInBST {// 记录上一个节点的值初始为极小值private Integer prev null;// 记录最小差值初始为最大值private int minDiff Integer.MAX_VALUE;public int minDiffInBST(TreeNode root) {// 调用中序遍历的辅助函数inOrder(root);return minDiff;}private void inOrder(TreeNode node) {if (node null) {return;}// 中序遍历左子树inOrder(node.left);// 处理当前节点if (prev ! null) {// 计算当前节点与上一个节点值的差并更新最小差值minDiff Math.min(minDiff, node.val - prev);}// 更新上一个节点值prev node.val;// 中序遍历右子树inOrder(node.right);} }87.二叉搜索树中第 K 小的元素中等 题目描述 给定一个二叉搜索树的根节点 root 和一个整数 k 请你设计一个算法查找其中第 k 小的元素从 1 开始计数。 示例 1 输入root [3,1,4,null,2], k 1 输出1示例 2 输入root [5,3,6,2,4,null,null,1], k 3 输出3提示 树中的节点数为 n 。1 k n 1040 Node.val 104 进阶如果二叉搜索树经常被修改插入/删除操作并且你需要频繁地查找第 k 小的值你将如何优化算法 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第44题。 88.验证二叉搜索树中等 题目描述 给你一个二叉树的根节点 root 判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下 节点的左子树只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左子树和右子树自身必须也是二叉搜索树。 示例 1 输入root [2,1,3] 输出true示例 2 输入root [5,1,4,null,null,3,6] 输出false 解释根节点的值是 5 但是右子节点的值是 4 。提示 树中节点数目范围在[1, 104] 内-231 Node.val 231 - 1 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第43题。 十二、图 89.岛屿数量中等 题目描述 给你一个由 1陆地和 0水组成的的二维网格请你计算网格中岛屿的数量。 岛屿总是被水包围并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 此外你可以假设该网格的四条边均被水包围。 示例 1 输入grid [[1,1,1,1,0],[1,1,0,1,0],[1,1,0,0,0],[0,0,0,0,0] ] 输出1示例 2 输入grid [[1,1,0,0,0],[1,1,0,0,0],[0,0,1,0,0],[0,0,0,1,1] ] 输出3提示 m grid.lengthn grid[i].length1 m, n 300grid[i][j] 的值为 0 或 1 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第51题。 也可见图论总结与编程练习_编程 图论-CSDN博客中的第3题。 90.被围绕的区域中等 题目描述 给你一个 m x n 的矩阵 board 由若干字符 X 和 O 组成捕获 所有 被围绕的区域 连接一个单元格与水平或垂直方向上相邻的单元格连接。区域连接所有 O 的单元格来形成一个区域。围绕如果您可以用 X 单元格 连接这个区域并且区域中没有任何单元格位于 board 边缘则该区域被 X 单元格围绕。 通过将输入矩阵 board 中的所有 O 替换为 X 来 捕获被围绕的区域。 示例 1 输入board [[X,X,X,X],[X,O,O,X],[X,X,O,X],[X,O,X,X]] 输出[[X,X,X,X],[X,X,X,X],[X,X,X,X],[X,O,X,X]] 解释 在上图中底部的区域没有被捕获因为它在 board 的边缘并且不能被围绕。 示例 2输入board [[X]] 输出[[X]] 提示 m board.lengthn board[i].length1 m, n 200board[i][j] 为 X 或 O 解题思路 要解决这个问题我们可以使用以下步骤 标记边缘的 O: 首先遍历矩阵的边缘上下左右四条边对所有边缘的 O 进行深度优先搜索DFS或广度优先搜索BFS将所有能够连接到边缘的 O 标记为特殊字符例如 #。这些标记的 O 表示它们不被完全围绕。 替换内部的 O: 遍历整个矩阵将所有未被标记的 O 替换为 X因为这些 O 是被完全围绕的区域。 恢复边缘的 O: 将之前标记的 # 恢复为 O以还原这些区域的原始状态。 复杂度分析 时间复杂度: O(m * n)其中 m 和 n 分别是矩阵的行数和列数。我们需要遍历矩阵的每个位置多次标记、替换、恢复每个位置操作的时间复杂度为 O(1)。 空间复杂度: O(m * n)主要用于存储矩阵和标记的临时空间。 代码实现 package org.zyf.javabasic.letcode.jd150.graph;/*** program: zyfboot-javabasic* description: 被围绕的区域* author: zhangyanfeng* create: 2024-08-25 14:58**/ public class Solve {public void solve(char[][] board) {if (board null || board.length 0 || board[0].length 0) {return;}int m board.length;int n board[0].length;// 使用 DFS 标记边缘的 Ofor (int i 0; i m; i) {if (board[i][0] O) {dfs(board, i, 0);}if (board[i][n - 1] O) {dfs(board, i, n - 1);}}for (int j 0; j n; j) {if (board[0][j] O) {dfs(board, 0, j);}if (board[m - 1][j] O) {dfs(board, m - 1, j);}}// 将内部的 O 替换为 X恢复边缘的 Ofor (int i 0; i m; i) {for (int j 0; j n; j) {if (board[i][j] O) {board[i][j] X;} else if (board[i][j] #) {board[i][j] O;}}}}private void dfs(char[][] board, int i, int j) {int m board.length;int n board[0].length;if (i 0 || i m || j 0 || j n || board[i][j] ! O) {return;}// 标记为 #board[i][j] #;// 递归四个方向dfs(board, i - 1, j);dfs(board, i 1, j);dfs(board, i, j - 1);dfs(board, i, j 1);} }91.克隆图中等 题目描述 给你无向 连通 图中一个节点的引用请你返回该图的 深拷贝克隆。 图中的每个节点都包含它的值 valint 和其邻居的列表list[Node]。 class Node {public int val;public ListNode neighbors; }测试用例格式 简单起见每个节点的值都和它的索引相同。例如第一个节点值为 1val 1第二个节点值为 2val 2以此类推。该图在测试用例中使用邻接列表表示。 邻接列表 是用于表示有限图的无序列表的集合。每个列表都描述了图中节点的邻居集。 给定节点将始终是图中的第一个节点值为 1。你必须将 给定节点的拷贝 作为对克隆图的引用返回。 示例 1 输入adjList [[2,4],[1,3],[2,4],[1,3]] 输出[[2,4],[1,3],[2,4],[1,3]] 解释 图中有 4 个节点。 节点 1 的值是 1它有两个邻居节点 2 和 4 。 节点 2 的值是 2它有两个邻居节点 1 和 3 。 节点 3 的值是 3它有两个邻居节点 2 和 4 。 节点 4 的值是 4它有两个邻居节点 1 和 3 。示例 2 输入adjList [[]] 输出[[]] 解释输入包含一个空列表。该图仅仅只有一个值为 1 的节点它没有任何邻居。示例 3输入adjList [] 输出[] 解释这个图是空的它不含任何节点。 提示 这张图中的节点数在 [0, 100] 之间。1 Node.val 100每个节点值 Node.val 都是唯一的图中没有重复的边也没有自环。图是连通图你可以从给定节点访问到所有节点。 解题思路 也可见图论总结与编程练习_编程 图论-CSDN博客中的第2题。 92.除法求值中等 题目描述 给你一个变量对数组 equations 和一个实数值数组 values 作为已知条件其中 equations[i] [Ai, Bi] 和 values[i] 共同表示等式 Ai / Bi values[i] 。每个 Ai 或 Bi 是一个表示单个变量的字符串。 另有一些以数组 queries 表示的问题其中 queries[j] [Cj, Dj] 表示第 j 个问题请你根据已知条件找出 Cj / Dj ? 的结果作为答案。 返回 所有问题的答案 。如果存在某个无法确定的答案则用 -1.0 替代这个答案。如果问题中出现了给定的已知条件中没有出现的字符串也需要用 -1.0 替代这个答案。 注意输入总是有效的。你可以假设除法运算中不会出现除数为 0 的情况且不存在任何矛盾的结果。 注意未在等式列表中出现的变量是未定义的因此无法确定它们的答案。 示例 1输入equations [[a,b],[b,c]], values [2.0,3.0], queries [[a,c],[b,a],[a,e],[a,a],[x,x]] 输出[6.00000,0.50000,-1.00000,1.00000,-1.00000] 解释 条件a / b 2.0, b / c 3.0 问题a / c ?, b / a ?, a / e ?, a / a ?, x / x ? 结果[6.0, 0.5, -1.0, 1.0, -1.0 ] 注意x 是未定义的 -1.0 示例 2输入equations [[a,b],[b,c],[bc,cd]], values [1.5,2.5,5.0], queries [[a,c],[c,b],[bc,cd],[cd,bc]] 输出[3.75000,0.40000,5.00000,0.20000] 示例 3输入equations [[a,b]], values [0.5], queries [[a,b],[b,a],[a,c],[x,y]] 输出[0.50000,2.00000,-1.00000,-1.00000] 提示 1 equations.length 20equations[i].length 21 Ai.length, Bi.length 5values.length equations.length0.0 values[i] 20.01 queries.length 20queries[i].length 21 Cj.length, Dj.length 5Ai, Bi, Cj, Dj 由小写英文字母与数字组成 解题思路 可见LeetCode 精选 75 回顾-CSDN博客中的第46题。 93.课程表中等 题目描述 你这个学期必须选修 numCourses 门课程记为 0 到 numCourses - 1 。 在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出其中 prerequisites[i] [ai, bi] 表示如果要学习课程 ai 则 必须 先学习课程  bi 。 例如先修课程对 [0, 1] 表示想要学习课程 0 你需要先完成课程 1 。 请你判断是否可能完成所有课程的学习如果可以返回 true 否则返回 false 。 示例 1输入numCourses 2, prerequisites [[1,0]] 输出true 解释总共有 2 门课程。学习课程 1 之前你需要完成课程 0 。这是可能的。 示例 2输入numCourses 2, prerequisites [[1,0],[0,1]] 输出false 解释总共有 2 门课程。学习课程 1 之前你需要先完成​课程 0 并且学习课程 0 之前你还应先完成课程 1 。这是不可能的。 提示 1 numCourses 20000 prerequisites.length 5000prerequisites[i].length 20 ai, bi numCoursesprerequisites[i] 中的所有课程对 互不相同 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第53题。 94.课程表 II中等 题目描述 现在你总共有 numCourses 门课需要选记为 0 到 numCourses - 1。给你一个数组 prerequisites 其中 prerequisites[i] [ai, bi] 表示在选修课程 ai 前 必须 先选修 bi 。 例如想要学习课程 0 你需要先完成课程 1 我们用一个匹配来表示[0,1] 。 返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序你只要返回 任意一种 就可以了。如果不可能完成所有课程返回 一个空数组 。 示例 1输入numCourses 2, prerequisites [[1,0]] 输出[0,1] 解释总共有 2 门课程。要学习课程 1你需要先完成课程 0。因此正确的课程顺序为 [0,1] 。 示例 2输入numCourses 4, prerequisites [[1,0],[2,0],[3,1],[3,2]] 输出[0,2,1,3] 解释总共有 4 门课程。要学习课程 3你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。 因此一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。 示例 3输入numCourses 1, prerequisites [] 输出[0] 提示 1 numCourses 20000 prerequisites.length numCourses * (numCourses - 1)prerequisites[i].length 20 ai, bi numCoursesai ! bi所有[ai, bi] 互不相同 解题思路 这个问题可以转化为 拓扑排序 问题其中每门课程和其前置课程形成一个有向图我们需要对这个有向图进行拓扑排序来确定课程的学习顺序。这里使用Kahn算法基于入度的拓扑排序算法来实现这一过程步骤如下 构建图使用邻接表来表示图其中每个节点课程指向它的后续课程。同时计算每个节点的入度即有多少课程依赖于这个课程。 初始化队列将所有入度为 0 的节点课程加入队列因为这些课程没有前置课程可以直接学习。 执行拓扑排序从队列中取出节点并将其添加到拓扑排序结果中。对于每个出队的节点减少其所有邻居节点的入度并将入度变为 0 的邻居节点加入队列。 检查结果如果拓扑排序结果中的节点数量等于总节点数量说明可以完成所有课程返回排序结果。如果节点数量不等于总节点数量说明存在环无法完成所有课程返回空数组。 复杂度分析 时间复杂度O(V E)其中 V 是图中的节点数课程数E 是边数先修关系。构建图和计算入度的时间复杂度为 O(V E)拓扑排序的时间复杂度也是 O(V E)。 空间复杂度O(V E)用于存储图的邻接表、入度数组以及队列。 代码实现 package org.zyf.javabasic.letcode.jd150.graph;import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Queue;/*** program: zyfboot-javabasic* description: 课程表 II* author: zhangyanfeng* create: 2024-08-25 17:33**/ public class FindOrder {public int[] findOrder(int numCourses, int[][] prerequisites) {// 初始化图的邻接表和入度数组ListInteger[] graph new ArrayList[numCourses];int[] inDegree new int[numCourses];for (int i 0; i numCourses; i) {graph[i] new ArrayList();}// 构建图和计算每个节点的入度for (int[] pair : prerequisites) {int dest pair[0];int src pair[1];graph[src].add(dest);inDegree[dest];}// 初始化队列并将所有入度为0的节点入队QueueInteger queue new LinkedList();for (int i 0; i numCourses; i) {if (inDegree[i] 0) {queue.offer(i);}}// 执行拓扑排序ListInteger result new ArrayList();while (!queue.isEmpty()) {int node queue.poll();result.add(node);for (int neighbor : graph[node]) {inDegree[neighbor]--;if (inDegree[neighbor] 0) {queue.offer(neighbor);}}}// 如果排序结果的节点数等于总课程数则返回结果否则返回空数组if (result.size() numCourses) {return result.stream().mapToInt(i - i).toArray();} else {return new int[0];}} }十三、图的广度优先搜索 95.蛇梯棋中等 题目描述 给你一个大小为 n x n 的整数矩阵 board 方格按从 1 到 n2 编号编号遵循 转行交替方式 从左下角开始 即从 board[n - 1][0] 开始每一行交替方向。 玩家从棋盘上的方格 1 总是在最后一行、第一列开始出发。 每一回合玩家需要从当前方格 curr 开始出发按下述要求前进 选定目标方格 next 目标方格的编号符合范围 [curr 1, min(curr 6, n2)] 。 该选择模拟了掷 六面体骰子 的情景无论棋盘大小如何玩家最多只能有 6 个目的地。传送玩家如果目标方格 next 处存在蛇或梯子那么玩家会传送到蛇或梯子的目的地。否则玩家传送到目标方格 next 。 当玩家到达编号 n2 的方格时游戏结束。 r 行 c 列的棋盘按前述方法编号棋盘格中可能存在 “蛇” 或 “梯子”如果 board[r][c] ! -1那个蛇或梯子的目的地将会是 board[r][c]。编号为 1 和 n2 的方格不是任何蛇或梯子的起点。 注意玩家在每回合的前进过程中最多只能爬过蛇或梯子一次就算目的地是另一条蛇或梯子的起点玩家也 不能 继续移动。 举个例子假设棋盘是 [[-1,4],[-1,3]] 第一次移动玩家的目标方格是 2 。那么这个玩家将会顺着梯子到达方格 3 但 不能 顺着方格 3 上的梯子前往方格 4 。 返回达到编号为 n2 的方格所需的最少移动次数如果不可能则返回 -1。 示例 1 输入board [[-1,-1,-1,-1,-1,-1],[-1,-1,-1,-1,-1,-1],[-1,-1,-1,-1,-1,-1],[-1,35,-1,-1,13,-1],[-1,-1,-1,-1,-1,-1],[-1,15,-1,-1,-1,-1]] 输出4 解释 首先从方格 1 [第 5 行第 0 列] 开始。 先决定移动到方格 2 并必须爬过梯子移动到到方格 15 。 然后决定移动到方格 17 [第 3 行第 4 列]必须爬过蛇到方格 13 。 接着决定移动到方格 14 且必须通过梯子移动到方格 35 。 最后决定移动到方格 36 , 游戏结束。 可以证明需要至少 4 次移动才能到达最后一个方格所以答案是 4 。 示例 2输入board [[-1,-1],[-1,3]] 输出1 提示 n board.length board[i].length2 n 20board[i][j] 的值是 -1 或在范围 [1, n2] 内编号为 1 和 n2 的方格上没有蛇或梯子 解题思路 要解决这个问题我们可以将其建模为一个图的最短路径问题使用广度优先搜索BFS来寻找从起点到终点的最短路径 构建图表示将二维矩阵转换为一维的节点编号。处理从每个节点可以跳转到的目标节点这些目标节点范围在 [curr 1, min(curr 6, n^2)] 之间。 处理蛇和梯子如果目标节点上有蛇或梯子则移动到目标节点的实际位置而不是目标节点的编号。 广度优先搜索BFS使用 BFS 从起点节点编号为 1开始探索记录每个节点的最短路径长度。每次从当前节点出发考虑所有可能的掷骰子结果最多 6 个节点。将每个可能的节点入队继续探索直到找到目标节点编号为 n^2或队列为空。 终止条件如果到达编号为 n^2 的节点返回所需的最小移动次数。如果 BFS 结束时仍未找到返回 -1。 复杂度分析 时间复杂度O(n^2)因为每个节点最多入队一次每次操作都在 O(1) 时间内完成。空间复杂度O(n^2)用于存储 BFS 队列和已访问节点。 代码实现 package org.zyf.javabasic.letcode.jd150.graph;import java.util.LinkedList; import java.util.Queue;/*** program: zyfboot-javabasic* description: 蛇梯棋* author: zhangyanfeng* create: 2024-08-25 17:38**/ public class SnakesAndLadders {public int snakesAndLadders(int[][] board) {int n board.length;// 将二维坐标转换为一维坐标的映射int[] flatBoard new int[n * n 1];boolean leftToRight true; // 矩阵从底行开始处理方向交替int index 1;for (int i n - 1; i 0; i--) {if (leftToRight) {for (int j 0; j n; j) {flatBoard[index] board[i][j];}} else {for (int j n - 1; j 0; j--) {flatBoard[index] board[i][j];}}leftToRight !leftToRight;}// BFS 初始化QueueInteger queue new LinkedList();boolean[] visited new boolean[n * n 1];queue.offer(1); // 从方格1开始visited[1] true;int moves 0;// BFS 开始while (!queue.isEmpty()) {int size queue.size();for (int i 0; i size; i) {int curr queue.poll();if (curr n * n) return moves; // 到达终点// 遍历所有可能的骰子结果for (int dice 1; dice 6; dice) {int next curr dice;if (next n * n) break; // 超过棋盘范围// 处理蛇或梯子if (flatBoard[next] ! -1) {next flatBoard[next];}if (!visited[next]) {visited[next] true;queue.offer(next);}}}moves;}return -1; // 无法到达终点} }96.最小基因变化中等 题目描述 基因序列可以表示为一条由 8 个字符组成的字符串其中每个字符都是 A、C、G 和 T 之一。 假设我们需要调查从基因序列 start 变为 end 所发生的基因变化。一次基因变化就意味着这个基因序列中的一个字符发生了变化。 例如AACCGGTT -- AACCGGTA 就是一次基因变化。 另有一个基因库 bank 记录了所有有效的基因变化只有基因库中的基因才是有效的基因序列。变化后的基因必须位于基因库 bank 中 给你两个基因序列 start 和 end 以及一个基因库 bank 请你找出并返回能够使 start 变化为 end 所需的最少变化次数。如果无法完成此基因变化返回 -1 。 注意起始基因序列 start 默认是有效的但是它并不一定会出现在基因库中。 示例 1输入start AACCGGTT, end AACCGGTA, bank [AACCGGTA] 输出1 示例 2输入start AACCGGTT, end AAACGGTA, bank [AACCGGTA,AACCGCTA,AAACGGTA] 输出2 示例 3输入start AAAAACCC, end AACCCCCC, bank [AAAACCCC,AAACCCCC,AACCCCCC] 输出3 提示 start.length 8end.length 80 bank.length 10bank[i].length 8start、end 和 bank[i] 仅由字符 [A, C, G, T] 组成 解题思路 要解决这个问题我们可以将其建模为一个图的最短路径问题其中每个基因序列是一个图中的节点每次变化表示从一个节点到另一个节点的边。由于每次基因变化只允许一个字符的变化因此图的边是通过改变一个字符来连接的: 建模为图每个基因序列视为图中的一个节点。从一个基因序列到另一个基因序列的边存在当且仅当这两个序列只有一个字符的差异。 使用广度优先搜索BFS从 start 基因序列开始使用 BFS 遍历所有可能的基因变化。使用一个队列来存储当前基因序列及其变化次数。使用一个集合来记录已访问的基因序列避免重复访问。 终止条件如果在 BFS 遍历过程中找到 end 基因序列返回当前的变化次数。如果 BFS 完成后未找到 end 基因序列返回 -1。 复杂度分析 时间复杂度O(N * L * 4^L)其中 N 是基因库的大小L 是基因序列的长度在此问题中是 8。对于每个基因序列我们需要检查其所有可能的变种4^L并在队列中进行 BFS 操作。空间复杂度O(N)用于存储基因库和已访问集合。 代码实现 package org.zyf.javabasic.letcode.jd150.graph;import java.util.*;/*** program: zyfboot-javabasic* description: 最小基因变化* author: zhangyanfeng* create: 2024-08-25 17:42**/ public class MinMutation {public int minMutation(String start, String end, String[] bank) {// 将基因库转为集合以便快速查找SetString bankSet new HashSet(Arrays.asList(bank));// 如果目标基因序列不在基因库中则直接返回 -1if (!bankSet.contains(end)) {return -1;}// 初始化 BFS 队列起始基因序列和变化次数为 0QueueString queue new LinkedList();queue.offer(start);int mutations 0;// 进行 BFSwhile (!queue.isEmpty()) {int size queue.size();for (int i 0; i size; i) {String curr queue.poll();// 如果当前基因序列等于目标基因序列则返回变化次数if (curr.equals(end)) {return mutations;}// 尝试每个可能的变化for (int j 0; j curr.length(); j) {char[] chars curr.toCharArray();for (char c A; c Z; c) {if (chars[j] c) continue; // 如果字符相同则跳过chars[j] c;String next new String(chars);// 如果变换后的基因序列在基因库中且未被访问过if (bankSet.contains(next)) {queue.offer(next);bankSet.remove(next); // 标记为已访问}}}}mutations; // 增加变化次数}// 如果无法到达目标基因序列返回 -1return -1;} }97.单词接龙困难 题目描述 字典 wordList 中从单词 beginWord 到 endWord 的 转换序列 是一个按下述规格形成的序列 beginWord - s1 - s2 - ... - sk 每一对相邻的单词只差一个字母。 对于 1 i k 时每个 si 都在 wordList 中。注意 beginWord 不需要在 wordList 中。sk  endWord 给你两个单词 beginWord 和 endWord 和一个字典 wordList 返回 从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列返回 0 。 示例 1输入beginWord hit, endWord cog, wordList [hot,dot,dog,lot,log,cog] 输出5 解释一个最短转换序列是 hit - hot - dot - dog - cog, 返回它的长度 5。 示例 2输入beginWord hit, endWord cog, wordList [hot,dot,dog,lot,log] 输出0 解释endWord cog 不在字典中所以无法进行转换。 提示 1 beginWord.length 10endWord.length beginWord.length1 wordList.length 5000wordList[i].length beginWord.lengthbeginWord、endWord 和 wordList[i] 由小写英文字母组成beginWord ! endWordwordList 中的所有字符串 互不相同 解题思路 也可见图论总结与编程练习_编程 图论-CSDN博客中的第1题。 十四、字典树 98.实现 Trie (前缀树)中等 题目描述 Trie发音类似 try或者说 前缀树 是一种树形数据结构用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景例如自动补全和拼写检查。 请你实现 Trie 类 Trie() 初始化前缀树对象。void insert(String word) 向前缀树中插入字符串 word 。boolean search(String word) 如果字符串 word 在前缀树中返回 true即在检索之前已经插入否则返回 false 。boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix 返回 true 否则返回 false 。 示例 输入 [Trie, insert, search, search, startsWith, insert, search] [[], [apple], [apple], [app], [app], [app], [app]] 输出 [null, null, true, false, true, null, true]解释 Trie trie new Trie(); trie.insert(apple); trie.search(apple); // 返回 True trie.search(app); // 返回 False trie.startsWith(app); // 返回 True trie.insert(app); trie.search(app); // 返回 True提示 1 word.length, prefix.length 2000word 和 prefix 仅由小写英文字母组成insert、search 和 startsWith 调用次数 总计 不超过 3 * 104 次 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第54题。 99.添加与搜索单词 - 数据结构设计中等 题目描述 请你设计一个数据结构支持 添加新单词 和 查找字符串是否与任何先前添加的字符串匹配 。 实现词典类 WordDictionary  WordDictionary() 初始化词典对象void addWord(word) 将 word 添加到数据结构中之后可以对它进行匹配bool search(word) 如果数据结构中存在字符串与 word 匹配则返回 true 否则返回  false 。word 中可能包含一些 . 每个 . 都可以表示任何一个字母。 示例输入 [WordDictionary,addWord,addWord,addWord,search,search,search,search] [[],[bad],[dad],[mad],[pad],[bad],[.ad],[b..]] 输出 [null,null,null,null,false,true,true,true] 解释 WordDictionary wordDictionary new WordDictionary(); wordDictionary.addWord(bad); wordDictionary.addWord(dad); wordDictionary.addWord(mad); wordDictionary.search(pad); // 返回 False wordDictionary.search(bad); // 返回 True wordDictionary.search(.ad); // 返回 True wordDictionary.search(b..); // 返回 True 提示 1 word.length 25addWord 中的 word 由小写英文字母组成search 中的 word 由 . 或小写英文字母组成最多调用 104 次 addWord 和 search 解题思路 要实现一个支持添加单词和匹配单词的词典数据结构我们可以使用Trie前缀树 设计思路 Trie 数据结构每个节点代表一个字符节点的子节点表示继续匹配的字符。节点中保存一个布尔值 isEndOfWord 来标记是否有单词在该节点结束。 支持通配符匹配使用递归函数来处理 . 通配符尝试匹配所有可能的字符。在递归过程中当遇到 . 时需要遍历该节点的所有子节点。 操作细节 添加单词 (addWord)遍历每个字符将其插入到 Trie 中。最后一个字符的节点标记为 isEndOfWord。 查找单词 (search)使用递归方法进行深度优先搜索DFS。对于每个字符检查是否匹配如果是 .尝试所有子节点。 复杂度分析 时间复杂度 addWord 操作的时间复杂度为 O(L)其中 L 是单词的长度。search 操作的时间复杂度为 O(L * 4^L) 最坏情况下其中 L 是单词的长度因为每个 . 可能会展开到最多 4 种情况。 空间复杂度 addWord 操作的空间复杂度为 O(L * N)其中 N 是存储的单词数。search 操作的空间复杂度为 O(L) 用于递归调用栈。 代码实现 package org.zyf.javabasic.letcode.jd150.trie;import java.util.HashMap; import java.util.Map;/*** program: zyfboot-javabasic* description: 添加与搜索单词 - 数据结构设计* author: zhangyanfeng* create: 2024-08-25 17:50**/ public class WordDictionary {private class TrieNode {MapCharacter, TrieNode children;boolean isEndOfWord;TrieNode() {children new HashMap();isEndOfWord false;}}private TrieNode root;public WordDictionary() {root new TrieNode();}public void addWord(String word) {TrieNode node root;for (char c : word.toCharArray()) {node node.children.computeIfAbsent(c, k - new TrieNode());}node.isEndOfWord true;}public boolean search(String word) {return searchInTrie(word, 0, root);}private boolean searchInTrie(String word, int index, TrieNode node) {if (index word.length()) {return node.isEndOfWord;}char c word.charAt(index);if (c .) {// 对于 ., 遍历所有子节点for (TrieNode child : node.children.values()) {if (searchInTrie(word, index 1, child)) {return true;}}return false;} else {// 对于普通字符, 只在子节点中查找TrieNode child node.children.get(c);if (child null) {return false;}return searchInTrie(word, index 1, child);}} }100.单词搜索 II困难 题目描述 给定一个 m x n 二维字符网格 board 和一个单词字符串列表 words 返回所有二维网格上的单词 。 单词必须按照字母顺序通过 相邻的单元格 内的字母构成其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。 示例 1 输入board [[o,a,a,n],[e,t,a,e],[i,h,k,r],[i,f,l,v]], words [oath,pea,eat,rain] 输出[eat,oath]示例 2 输入board [[a,b],[c,d]], words [abcb] 输出[]提示 m board.lengthn board[i].length1 m, n 12board[i][j] 是一个小写英文字母1 words.length 3 * 1041 words[i].length 10words[i] 由小写英文字母组成words 中的所有字符串互不相同 解题思路 这个问题是经典的单词搜索问题我们可以通过 Trie前缀树结合 DFS深度优先搜索来高效解决 Trie前缀树构建使用 Trie 数据结构存储所有单词以支持高效的前缀匹配。每个 Trie 节点代表一个字符。将每个单词插入 Trie 中在单词的最后一个节点处标记该单词。 DFS 搜索在网格的每个单元格开始进行 DFS 搜索尝试从当前单元格出发构建单词。通过检查当前单元格的字符是否存在于当前 Trie 节点的子节点中来决定是否继续搜索。使用回溯将当前单元格的字符恢复为原始字符来确保每个单元格在搜索过程中只被访问一次。 回溯和标记在 DFS 搜索过程中将当前单元格标记为访问过用特殊字符 # 替代原字符以避免重复使用。搜索完成后将单元格状态恢复为原始字符。 复杂度分析 时间复杂度 构建 Trie 的时间复杂度为 O(W⋅L))其中 WWW 是单词的数量L 是单词的平均长度。在网格上进行 DFS 的时间复杂度为 O(m⋅n⋅4^L)其中 m 和 n 是网格的行列数L 是单词的最大长度。每个单元格在 DFS 中最多有 4 个方向进行探索。 空间复杂度 Trie 的空间复杂度为 O(W⋅L)。DFS 递归栈的空间复杂度为 O(L)加上网格的访问标记空间 O(m⋅n)。 代码实现 package org.zyf.javabasic.letcode.jd150.trie;import java.util.*;/*** program: zyfboot-javabasic* description: 单词搜索 II* author: zhangyanfeng* create: 2024-08-25 17:57**/ public class FindWords {// 定义四个方向上下左右int[][] dirs {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};public ListString findWords(char[][] board, String[] words) {Trie trie new Trie(); // 创建 Trie 树// 将所有单词插入 Trie 中for (String word : words) {trie.insert(word);}SetString ans new HashSet(); // 存储结果的集合// 遍历网格的每一个单元格进行 DFS 搜索for (int i 0; i board.length; i) {for (int j 0; j board[0].length; j) {dfs(board, trie, i, j, ans);}}return new ArrayList(ans); // 返回结果的列表}// 深度优先搜索函数public void dfs(char[][] board, Trie now, int i1, int j1, SetString ans) {// 如果当前字符不在 Trie 的子节点中则返回if (!now.children.containsKey(board[i1][j1])) {return;}char ch board[i1][j1]; // 当前字符now now.children.get(ch); // 移动到子节点// 如果当前节点是一个单词的结束点则将单词添加到结果集中if (!.equals(now.word)) {ans.add(now.word);}// 标记当前单元格为访问过board[i1][j1] #;// 遍历四个方向进行 DFS 搜索for (int[] dir : dirs) {int i2 i1 dir[0], j2 j1 dir[1];if (i2 0 i2 board.length j2 0 j2 board[0].length) {dfs(board, now, i2, j2, ans);}}// 恢复单元格状态为原始字符board[i1][j1] ch;}// Trie前缀树实现class Trie {String word; // 存储单词MapCharacter, Trie children; // 存储子节点boolean isWord; // 标记是否是单词的结束public Trie() {this.word ;this.children new HashMap();}// 将单词插入到 Trie 中public void insert(String word) {Trie cur this;for (int i 0; i word.length(); i) {char c word.charAt(i);if (!cur.children.containsKey(c)) {cur.children.put(c, new Trie());}cur cur.children.get(c);}cur.word word; // 设置单词结束标记}} }十五、回溯 101.电话号码的字母组合中等 题目描述 给定一个仅包含数字 2-9 的字符串返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下与电话按键相同。注意 1 不对应任何字母。 示例 1输入digits 23 输出[ad,ae,af,bd,be,bf,cd,ce,cf] 示例 2输入digits  输出[] 示例 3输入digits 2 输出[a,b,c] 提示 0 digits.length 4digits[i] 是范围 [2, 9] 的一个数字。 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第57题。 可见LeetCode 精选 75 回顾-CSDN博客中的第57题。 102.组合 中等 题目描述 给定两个整数 n 和 k返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 示例 1输入n 4, k 2 输出 [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ] 示例 2输入n 1, k 1 输出[[1]] 提示 1 n 201 k n 解题思路 这个问题要求我们从 [1, n] 范围内的数字中选取 k 个数的所有可能组合。解决这个问题可以使用 回溯法 选择从当前数字中选择一个数字加入当前组合。探索递归地继续选择下一个数字。回溯撤销上一步的选择并继续尝试其他选择。 具体步骤如下 初始化开始时选择从 1 到 n 的数字作为起点。递归选择在每一步递归中从当前数字开始尝试选择 k 个数字中的下一个数字。终止条件当选择的数字数量等于 k 时保存当前组合。撤销选择在回溯时撤销上一步的选择以尝试其他组合。 复杂度分析 时间复杂度 生成组合的总时间复杂度是 O(C(n, k))其中 C(n, k) 是组合数表示从 n 个元素中选择 k 个的组合数。计算组合数 C(n, k) 的公式为 n! / (k! * (n - k)!)。 空间复杂度 递归栈的空间复杂度为 O(k)因为在最坏情况下递归深度为 k。存储所有组合的空间复杂度为 O(C(n, k) * k)因为我们需要存储所有的组合每个组合的大小为 k。 代码实现 package org.zyf.javabasic.letcode.jd150.blacktracing;import java.util.ArrayList; import java.util.List;/*** program: zyfboot-javabasic* description: 组合* author: zhangyanfeng* create: 2024-08-25 18:04**/ public class Combine {public ListListInteger combine(int n, int k) {ListListInteger result new ArrayList(); // 存储所有组合ListInteger combination new ArrayList(); // 当前组合backtrack(result, combination, n, k, 1); // 从数字1开始递归return result;}// 回溯函数private void backtrack(ListListInteger result, ListInteger combination, int n, int k, int start) {// 如果当前组合的大小等于k添加到结果列表中if (combination.size() k) {result.add(new ArrayList(combination)); // 添加一份当前组合的副本return;}// 从start开始尝试选择每个数字for (int i start; i n; i) {combination.add(i); // 选择当前数字backtrack(result, combination, n, k, i 1); // 递归选择下一个数字combination.remove(combination.size() - 1); // 撤销选择回溯}} }103.全排列中等 题目描述 给定一个不含重复数字的数组 nums 返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1输入nums [1,2,3] 输出[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] 示例 2输入nums [0,1] 输出[[0,1],[1,0]] 示例 3输入nums [1] 输出[[1]] 提示 1 nums.length 6-10 nums[i] 10nums 中的所有整数 互不相同 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第55题。 104.组合总和中等 题目描述 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target 找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同则两种组合是不同的。  对于给定的输入保证和为 target 的不同组合数少于 150 个。 示例 1输入candidates  [2,3,6,7], target  7 输出[[2,2,3],[7]] 解释 2 和 3 可以形成一组候选2 2 3 7 。注意 2 可以使用多次。 7 也是一个候选 7 7 。 仅有这两种组合。 示例 2输入: candidates [2,3,5], target 8 输出: [[2,2,2,2],[2,3,3],[3,5]] 示例 3输入: candidates  [2], target 1 输出: [] 提示 1 candidates.length 302 candidates[i] 40candidates 的所有元素 互不相同1 target 40 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第58题。 105.N 皇后 II困难 题目描述 n 皇后问题 研究的是如何将 n 个皇后放置在 n × n 的棋盘上并且使皇后彼此之间不能相互攻击。 给你一个整数 n 返回 n 皇后问题 不同的解决方案的数量。 示例 1 输入n 4 输出2 解释如上图所示4 皇后问题存在两个不同的解法。示例 2输入n 1 输出1 提示 1 n 9 解题思路 n 皇后问题的目标是将 n 个皇后放置在一个 n x n 的棋盘上确保没有任何两个皇后在同一行、同一列或同一对角线上。每个皇后都可以攻击棋盘上与它处于同一行、列或对角线上的其他皇后。 要解决这个问题我们可以使用 回溯法Backtracking这种方法通过逐步构建解并在发现不满足条件时进行撤销来逐步寻找所有可能的解。回溯法步骤 选择逐行放置皇后并在每一行中尝试放置皇后的位置。约束在尝试放置皇后时确保它不与已放置的皇后冲突。递归对每一个合法的位置递归地放置下一个皇后。回溯当所有行都尝试完毕或者发现当前放置导致冲突时撤销当前选择回到上一步继续尝试其他选择。 复杂度分析 时间复杂度在最坏情况下时间复杂度为 O(N!)因为对于每一行可能需要尝试 N 个位置并且最多有 N! 种放置方案。 空间复杂度主要包括递归栈的深度递归深度为 O(N)以及用于存储解决方案的空间最多有 O(N!) 种解决方案。 代码实现 package org.zyf.javabasic.letcode.jd150.blacktracing;import java.util.ArrayList; import java.util.List;/*** program: zyfboot-javabasic* description: N 皇后 II* author: zhangyanfeng* create: 2024-08-25 18:11**/ public class TotalNQueens {public int totalNQueens(int n) {ListListString solutions new ArrayList(); // 存储所有解决方案backtrack(solutions, new ArrayList(), n, new boolean[n], new boolean[2 * n - 1], new boolean[2 * n - 1], 0);return solutions.size(); // 返回解决方案的数量}private void backtrack(ListListString solutions, ListString board, int n, boolean[] cols, boolean[] diag1, boolean[] diag2, int row) {// 递归终止条件如果所有行都被处理完毕if (row n) {solutions.add(new ArrayList(board)); // 记录当前有效的解决方案return;}// 尝试在当前行的每一列放置皇后for (int col 0; col n; col) {int d1 row - col (n - 1); // 主要对角线索引int d2 row col; // 副对角线索引// 检查当前位置是否安全if (!cols[col] !diag1[d1] !diag2[d2]) {char[] rowArray new char[n];for (int i 0; i n; i) {rowArray[i] .; // 初始化当前行}rowArray[col] Q; // 放置皇后board.add(new String(rowArray)); // 记录当前行的字符串形式// 标记当前位置为占用cols[col] true;diag1[d1] true;diag2[d2] true;// 递归处理下一行backtrack(solutions, board, n, cols, diag1, diag2, row 1);// 撤销当前选择board.remove(board.size() - 1);cols[col] false;diag1[d1] false;diag2[d2] false;}}} }106.括号生成中等 题目描述 数字 n 代表生成括号的对数请你设计一个函数用于能够生成所有可能的并且 有效的 括号组合。 示例 1输入n 3 输出[((())),(()()),(())(),()(()),()()()] 示例 2输入n 1 输出[()] 提示 1 n 8 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第59题。 107.单词搜索中等 题目描述 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中返回 true 否则返回 false 。 单词必须按照字母顺序通过相邻的单元格内的字母构成其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。 示例 1 输入board [[A,B,C,E],[S,F,C,S],[A,D,E,E]], word ABCCED 输出true示例 2 输入board [[A,B,C,E],[S,F,C,S],[A,D,E,E]], word SEE 输出true示例 3 输入board [[A,B,C,E],[S,F,C,S],[A,D,E,E]], word ABCB 输出false提示 m board.lengthn board[i].length1 m, n 61 word.length 15board 和 word 仅由大小写英文字母组成 进阶你可以使用搜索剪枝的技术来优化解决方案使其在 board 更大的情况下可以更快解决问题 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第60题。 十六、分治 108.将有序数组转换为二叉搜索树简单 题目描述 给你一个整数数组 nums 其中元素已经按 升序 排列请你将其转换为一棵 平衡 二叉搜索树。 示例 1 输入nums [-10,-3,0,5,9] 输出[0,-3,9,-10,null,5] 解释[0,-10,5,null,-3,null,9] 也将被视为正确答案示例 2 输入nums [1,3] 输出[3,1] 解释[1,null,3] 和 [3,1] 都是高度平衡二叉搜索树。提示 1 nums.length 104-104 nums[i] 104nums 按 严格递增 顺序排列 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第42题。 109.排序链表中等 题目描述 给你链表的头结点 head 请将其按 升序 排列并返回 排序后的链表 。 示例 1 输入head [4,2,1,3] 输出[1,2,3,4]示例 2 输入head [-1,5,3,4,0] 输出[-1,0,3,4,5]示例 3输入head [] 输出[] 提示 链表中节点的数目在范围 [0, 5 * 104] 内-105  Node.val 105 进阶你可以在 O(n log n) 时间复杂度和常数级空间复杂度下对链表进行排序吗 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第33题。 110.建立四叉树中等 题目描述 给你一个 n * n 矩阵 grid 矩阵由若干 0 和 1 组成。请你用四叉树表示该矩阵 grid 。 你需要返回能表示矩阵 grid 的 四叉树 的根结点。 四叉树数据结构中每个内部节点只有四个子节点。此外每个节点都有两个属性 val储存叶子结点所代表的区域的值。1 对应 True0 对应 False。注意当 isLeaf 为 False 时你可以把 True 或者 False 赋值给节点两种值都会被判题机制 接受 。isLeaf: 当这个节点是一个叶子结点时为 True如果它有 4 个子节点则为 False 。 class Node {public boolean val;public boolean isLeaf;public Node topLeft;public Node topRight;public Node bottomLeft;public Node bottomRight; } 我们可以按以下步骤为二维区域构建四叉树 如果当前网格的值相同即全为 0 或者全为 1将 isLeaf 设为 True 将 val 设为网格相应的值并将四个子节点都设为 Null 然后停止。如果当前网格的值不同将 isLeaf 设为 False 将 val 设为任意值然后如下图所示将当前网格划分为四个子网格。使用适当的子网格递归每个子节点。 如果你想了解更多关于四叉树的内容可以参考 wiki 。 四叉树格式 你不需要阅读本节来解决这个问题。只有当你想了解输出格式时才会这样做。输出为使用层序遍历后四叉树的序列化形式其中 null 表示路径终止符其下面不存在节点。 它与二叉树的序列化非常相似。唯一的区别是节点以列表形式表示 [isLeaf, val] 。 如果 isLeaf 或者 val 的值为 True 则表示它在列表 [isLeaf, val] 中的值为 1 如果 isLeaf 或者 val 的值为 False 则表示值为 0 。 示例 1 输入grid [[0,1],[1,0]] 输出[[0,1],[1,0],[1,1],[1,1],[1,0]] 解释此示例的解释如下 请注意在下面四叉树的图示中0 表示 false1 表示 True 。示例 2 输入grid [[1,1,1,1,0,0,0,0],[1,1,1,1,0,0,0,0],[1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1],[1,1,1,1,0,0,0,0],[1,1,1,1,0,0,0,0],[1,1,1,1,0,0,0,0],[1,1,1,1,0,0,0,0]] 输出[[0,1],[1,1],[0,1],[1,1],[1,0],null,null,null,null,[1,0],[1,0],[1,1],[1,1]] 解释网格中的所有值都不相同。我们将网格划分为四个子网格。 topLeftbottomLeft 和 bottomRight 均具有相同的值。 topRight 具有不同的值因此我们将其再分为 4 个子网格这样每个子网格都具有相同的值。 解释如下图所示提示 n grid.length grid[i].lengthn 2x 其中 0 x 6 解题思路 要将一个 n x n 的矩阵转换为四叉树QuadTree四叉树是一种用于表示二维空间的树形结构其中每个节点代表一个矩形区域。具体到这个问题 检查当前矩形区域 如果当前区域内所有的值都相同则该区域可以直接作为一个叶子节点isLeaf 为 True并将值val设置为这个区域的值。如果当前区域内的值不同则需要将其划分为四个子区域分别递归处理每个子区域。 递归构建四叉树 将当前矩形区域分成四个子区域topLeft, topRight, bottomLeft, bottomRight。对每个子区域递归调用构建四叉树的过程。当递归处理完四个子区域后将它们作为当前节点的四个子节点。 复杂度分析 时间复杂度最坏情况下每个节点都需要将整个区域划分为四个子区域。这种情况下总体复杂度是 O(N^2)其中 N 是矩阵的边长因为每个节点的处理和递归需要 O(N^2) 时间。 空间复杂度递归调用栈的深度最大为 O(log(N))。空间复杂度主要由树的节点数量和递归栈深度决定总体复杂度是 O(N^2)。 代码实现 package org.zyf.javabasic.letcode.jd150.tree;/*** program: zyfboot-javabasic* description: 建立四叉树* author: zhangyanfeng* create: 2024-08-25 18:23**/ public class BuildQuadTree {public Node construct(int[][] grid) {return buildQuadTree(grid, 0, 0, grid.length);}private Node buildQuadTree(int[][] grid, int row, int col, int size) {// 创建当前节点Node node new Node();// 检查当前区域是否为一个叶子节点if (isUniform(grid, row, col, size)) {node.isLeaf true;node.val grid[row][col] 1;return node;}// 当前区域不是一个叶子节点分为四个子区域node.isLeaf false;int halfSize size / 2;node.topLeft buildQuadTree(grid, row, col, halfSize);node.topRight buildQuadTree(grid, row, col halfSize, halfSize);node.bottomLeft buildQuadTree(grid, row halfSize, col, halfSize);node.bottomRight buildQuadTree(grid, row halfSize, col halfSize, halfSize);return node;}private boolean isUniform(int[][] grid, int row, int col, int size) {int firstValue grid[row][col];for (int r row; r row size; r) {for (int c col; c col size; c) {if (grid[r][c] ! firstValue) {return false;}}}return true;}class Node {public boolean val;public boolean isLeaf;public Node topLeft;public Node topRight;public Node bottomLeft;public Node bottomRight;public Node() {this.val false;this.isLeaf false;this.topLeft null;this.topRight null;this.bottomLeft null;this.bottomRight null;}} }111.合并 K 个升序链表 困难 题目描述 给你一个链表数组每个链表都已经按升序排列。 请你将所有链表合并到一个升序链表中返回合并后的链表。 示例 1 输入lists [[1,4,5],[1,3,4],[2,6]] 输出[1,1,2,3,4,4,5,6] 解释链表数组如下 [1-4-5,1-3-4,2-6 ] 将它们合并到一个有序链表中得到。 1-1-2-3-4-4-5-6示例 2输入lists [] 输出[] 示例 3输入lists [[]] 输出[] 提示 k lists.length0 k 10^40 lists[i].length 500-10^4 lists[i][j] 10^4lists[i] 按 升序 排列lists[i].length 的总和不超过 10^4 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第34题。 十七、Kadane 算法 112.最大子数组和 中等 题目描述 给你一个整数数组 nums 请你找出一个具有最大和的连续子数组子数组最少包含一个元素返回其最大和。 子数组 是数组中的一个连续部分。 示例 1输入nums [-2,1,-3,4,-1,2,1,-5,4]   输出6   解释连续子数组 [4,-1,2,1] 的和最大为 6 。 示例 2输入nums [1]   输出1 示例 3输入nums [5,4,-1,7,8]   输出23 提示 1 nums.length 105-104 nums[i] 104 进阶如果你已经实现复杂度为 O(n) 的解法尝试使用更为精妙的 分治法 求解。 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第13题。 113.环形子数组的最大和 题目描述 给定一个长度为 n 的环形整数数组 nums 返回 nums 的非空 子数组 的最大可能和 。 环形数组 意味着数组的末端将会与开头相连呈环状。形式上 nums[i] 的下一个元素是 nums[(i 1) % n]  nums[i] 的前一个元素是 nums[(i - 1 n) % n] 。 子数组 最多只能包含固定缓冲区 nums 中的每个元素一次。形式上对于子数组 nums[i], nums[i 1], ..., nums[j] 不存在 i k1, k2 j 其中 k1 % n k2 % n 。 示例 1输入nums [1,-2,3,-2] 输出3 解释从子数组 [3] 得到最大和 3 示例 2输入nums [5,-3,5] 输出10 解释从子数组 [5,5] 得到最大和 5 5 10 示例 3输入nums [3,-2,2,-3] 输出3 解释从子数组 [3] 和 [3,-2,2] 都可以得到最大和 3 提示 n nums.length1 n 3 * 104-3 * 104  nums[i] 3 * 104 解题思路 为了解决环形数组的最大子数组和问题我们可以通过以下步骤来处理 非环形数组的最大子数组和 使用 Kadane 算法计算非环形数组的最大子数组和。这是因为 Kadane 算法可以在 O(n)O(n)O(n) 时间复杂度内找到最大和的子数组。 环形数组的最大子数组和 要找到环形数组的最大子数组和我们可以考虑两种情况 不包括环形部分这就是普通的最大子数组和已经在第一步中求出。包括环形部分可以通过以下步骤计算计算整个数组的总和 totalSum。使用 Kadane 算法计算数组的最小子数组和 minSum。最大环形子数组和  totalSum - minSum。这是因为要包括整个数组的环形部分我们可以从 totalSum 减去数组的最小子数组和得到环形部分的最大和。 特殊情况处理 如果整个数组只有一个元素则最大和只能是该元素本身。如果所有元素都是负数那么环形和不应该考虑因为环形和会减去最小子数组和而这是不需要的。 复杂度分析 时间复杂度O(n)因为每个步骤Kadane 算法计算总和计算最小子数组和都需要 O(n) 时间。空间复杂度O(1)只使用了常量级的额外空间。 代码实现 package org.zyf.javabasic.letcode.jd150.kadane;/*** program: zyfboot-javabasic* description: 环形子数组的最大和* author: zhangyanfeng* create: 2024-08-25 18:29**/ public class MaxSubarraySumCircular {public int maxSubarraySumCircular(int[] nums) {// 计算数组的总和int totalSum 0;for (int num : nums) {totalSum num;}// 求普通最大子数组和int maxSum kadane(nums, true);// 求最小子数组和int minSum kadane(nums, false);// 最大环形子数组和int maxCircularSum totalSum - minSum;// 如果数组中所有元素都为负数则 maxCircularSum 会是 0这种情况需要特殊处理if (maxCircularSum 0) {return maxSum;}// 返回最大值return Math.max(maxSum, maxCircularSum);}// Kadane 算法变种用于计算最大子数组和或最小子数组和private int kadane(int[] nums, boolean findMax) {int currentSum nums[0];int extremumSum nums[0];for (int i 1; i nums.length; i) {if (findMax) {currentSum Math.max(nums[i], currentSum nums[i]);extremumSum Math.max(extremumSum, currentSum);} else {currentSum Math.min(nums[i], currentSum nums[i]);extremumSum Math.min(extremumSum, currentSum);}}return extremumSum;} }十八、二分查找 114.搜索插入位置简单 题目描述 给定一个排序数组和一个目标值在数组中找到目标值并返回其索引。如果目标值不存在于数组中返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 示例 1:输入: nums [1,3,5,6], target 5 输出: 2 示例 2:输入: nums [1,3,5,6], target 2 输出: 1 示例 3:输入: nums [1,3,5,6], target 7 输出: 4 提示: 1 nums.length 104-104 nums[i] 104nums 为 无重复元素 的 升序 排列数组-104 target 104 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第63题。 115.搜索二维矩阵中等 题目描述 给你一个满足下述两条属性的 m x n 整数矩阵 每行中的整数从左到右按非严格递增顺序排列。每行的第一个整数大于前一行的最后一个整数。 给你一个整数 target 如果 target 在矩阵中返回 true 否则返回 false 。 示例 1 输入matrix [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target 3 输出true示例 2 输入matrix [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target 13 输出false提示 m matrix.lengthn matrix[i].length1 m, n 100-104 matrix[i][j], target 104 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第65题。 116.寻找峰值中等 题目描述 峰值元素是指其值严格大于左右相邻值的元素。 给你一个整数数组 nums找到峰值元素并返回其索引。数组可能包含多个峰值在这种情况下返回 任何一个峰值 所在位置即可。 你可以假设 nums[-1] nums[n] -∞ 。 你必须实现时间复杂度为 O(log n) 的算法来解决此问题。 示例 1输入nums  [1,2,3,1] 输出2 解释3 是峰值元素你的函数应该返回其索引 2。 示例 2输入nums  [1,2,1,3,5,6,4] 输出1 或 5 解释你的函数可以返回索引 1其峰值元素为 2   或者返回索引 5 其峰值元素为 6。 提示 1 nums.length 1000-231 nums[i] 231 - 1对于所有有效的 i 都有 nums[i] ! nums[i 1] 解题思路 可见图论总结与编程练习_编程 图论-CSDN博客中的第55题。 117.搜索旋转排序数组中等 题目描述 整数数组 nums 按升序排列数组中的值 互不相同 。 在传递给函数之前nums 在预先未知的某个下标 k0 k nums.length上进行了 旋转使数组变为 [nums[k], nums[k1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]下标 从 0 开始 计数。例如 [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。 给你 旋转后 的数组 nums 和一个整数 target 如果 nums 中存在这个目标值 target 则返回它的下标否则返回 -1 。 你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。 示例 1输入nums [4,5,6,7,0,1,2], target 0 输出4 示例 2输入nums [4,5,6,7,0,1,2], target 3 输出-1 示例 3输入nums [1], target 0 输出-1 提示 1 nums.length 5000-104 nums[i] 104nums 中的每个值都 独一无二题目数据保证 nums 在预先未知的某个下标上进行了旋转-104 target 104 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第66题。 118.在排序数组中查找元素的第一个和最后一个位置中等 题目描述 给你一个按照非递减顺序排列的整数数组 nums和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target返回 [-1, -1]。 你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。 示例 1输入nums [5,7,7,8,8,10], target 8 输出[3,4] 示例 2输入nums [5,7,7,8,8,10], target 6 输出[-1,-1] 示例 3输入nums [], target 0 输出[-1,-1] 提示 0 nums.length 105-109  nums[i]  109nums 是一个非递减数组-109  target  109 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第65题。 119.寻找旋转排序数组中的最小值中等 题目描述 已知一个长度为 n 的数组预先按照升序排列经由 1 到 n 次 旋转 后得到输入数组。例如原数组 nums [0,1,2,4,5,6,7] 在变化后可能得到 若旋转 4 次则可以得到 [4,5,6,7,0,1,2]若旋转 7 次则可以得到 [0,1,2,4,5,6,7] 注意数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。 给你一个元素值 互不相同 的数组 nums 它原来是一个升序排列的数组并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。 你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。 示例 1输入nums [3,4,5,1,2] 输出1 解释原数组为 [1,2,3,4,5] 旋转 3 次得到输入数组。 示例 2输入nums [4,5,6,7,0,1,2] 输出0 解释原数组为 [0,1,2,4,5,6,7] 旋转 3 次得到输入数组。 示例 3输入nums [11,13,15,17] 输出11 解释原数组为 [11,13,15,17] 旋转 4 次得到输入数组。 提示 n nums.length1 n 5000-5000 nums[i] 5000nums 中的所有整数 互不相同nums 原来是一个升序排序的数组并进行了 1 至 n 次旋转 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第67题。 120.寻找两个正序数组的中位数 困难 题目描述 给定两个大小分别为 m 和 n 的正序从小到大数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。 算法的时间复杂度应该为 O(log (mn)) 。 示例 1输入nums1 [1,3], nums2 [2] 输出2.00000 解释合并数组 [1,2,3] 中位数 2 示例 2输入nums1 [1,2], nums2 [3,4] 输出2.50000 解释合并数组 [1,2,3,4] 中位数 (2 3) / 2 2.5 提示 nums1.length mnums2.length n0 m 10000 n 10001 m n 2000-106 nums1[i], nums2[i] 106 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第68题。 十九、堆 121.数组中的第K个最大元素中等 题目描述 给定整数数组 nums 和整数 k请返回数组中第 k 个最大的元素。 请注意你需要找的是数组排序后的第 k 个最大的元素而不是第 k 个不同的元素。 你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。 示例 1:输入: [3,2,1,5,6,4], k 2 输出: 5 示例 2:输入: [3,2,3,1,2,4,5,5,6], k 4 输出: 4 提示 1 k nums.length 105-104  nums[i] 104 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第74题。 122.IPO困难 题目描述 假设 力扣LeetCode即将开始 IPO 。为了以更高的价格将股票卖给风险投资公司力扣 希望在 IPO 之前开展一些项目以增加其资本。 由于资源有限它只能在 IPO 之前完成最多 k 个不同的项目。帮助 力扣 设计完成最多 k 个不同项目后得到最大总资本的方式。 给你 n 个项目。对于每个项目 i 它都有一个纯利润 profits[i] 和启动该项目需要的最小资本 capital[i] 。 最初你的资本为 w 。当你完成一个项目时你将获得纯利润且利润将被添加到你的总资本中。 总而言之从给定项目中选择 最多 k 个不同项目的列表以 最大化最终资本 并输出最终可获得的最多资本。 答案保证在 32 位有符号整数范围内。 示例 1输入k 2, w 0, profits [1,2,3], capital [0,1,1] 输出4 解释 由于你的初始资本为 0你仅可以从 0 号项目开始。 在完成后你将获得 1 的利润你的总资本将变为 1。 此时你可以选择开始 1 号或 2 号项目。 由于你最多可以选择两个项目所以你需要完成 2 号项目以获得最大的资本。 因此输出最后最大化的资本为 0 1 3 4。 示例 2输入k 3, w 0, profits [1,2,3], capital [0,1,2] 输出6 提示 1 k 1050 w 109n profits.lengthn capital.length1 n 1050 profits[i] 1040 capital[i] 109 解题思路 这个问题涉及到选择最多 k 个项目使得最终资本最大化。由于项目有启动资本限制我们需要在每个阶段选择当前资本 w 能够支持的、利润最高的项目。这就需要一种策略来动态选择项目。 项目按所需资本排序首先我们将所有项目按照所需的资本 capital[i] 进行排序。这样我们可以按顺序考虑可以启动的项目。 优先选择高利润项目为了在每次选择项目时获得最大的利润我们可以使用一个最大堆大根堆来存储当前资本范围内可启动项目的利润。 迭代选择项目 初始化时首先将当前资本 w 能够启动的所有项目的利润放入最大堆中。然后从堆中选出利润最高的项目更新资本 w并继续从剩余项目中添加能够启动的项目到堆中重复 k 次或直到没有更多项目可以选择。 复杂度分析 时间复杂度项目排序的时间复杂度为 O(nlog⁡n)O(n \log n)O(nlogn)每次从堆中选取项目并插入新项目的时间复杂度为 O(log⁡n)O(\log n)O(logn)最多执行 k 次因此整体时间复杂度为 O(nlog⁡nklog⁡n)O(n \log n k \log n)O(nlognklogn)。空间复杂度由于需要使用堆存储项目的利润空间复杂度为 O(n)O(n)O(n)。 代码实现 package org.zyf.javabasic.letcode.jd150.heap;import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.PriorityQueue;/*** program: zyfboot-javabasic* description: IPO* author: zhangyanfeng* create: 2024-08-25 18:43**/ public class MaximizedCapital {// 主方法用于计算在选择最多 k 个项目后可以获得的最大资本public int findMaximizedCapital(int k, int w, int[] profits, int[] capital) {// 项目数int n profits.length;// 项目列表每个项目包括资本需求和利润int[][] projects new int[n][2];for (int i 0; i n; i) {projects[i][0] capital[i]; // 资本需求projects[i][1] profits[i]; // 利润}// 按资本需求升序排序Arrays.sort(projects, Comparator.comparingInt(a - a[0]));// 最大堆用于存储当前资本 w 能够启动的项目利润PriorityQueueInteger maxHeap new PriorityQueue(Collections.reverseOrder());int index 0; // 用于遍历项目// 选择最多 k 个项目for (int i 0; i k; i) {// 将所有当前资本 w 能启动的项目的利润加入堆中while (index n projects[index][0] w) {maxHeap.offer(projects[index][1]);index;}// 如果堆中有可选项目选择利润最大的项目if (!maxHeap.isEmpty()) {w maxHeap.poll(); // 更新资本} else {break; // 如果没有更多可选项目直接结束}}return w; // 返回最终的资本}// 测试代码public static void main(String[] args) {MaximizedCapital solution new MaximizedCapital();int k 2, w 0;int[] profits {1, 2, 3};int[] capital {0, 1, 1};int result solution.findMaximizedCapital(k, w, profits, capital);System.out.println(result); // 输出应为 4} }123.查找和最小的 K 对数字中等 题目描述 给定两个以 非递减顺序排列 的整数数组 nums1 和 nums2 , 以及一个整数 k 。 定义一对值 (u,v)其中第一个元素来自 nums1第二个元素来自 nums2 。 请找到和最小的 k 个数对 (u1,v1),  (u2,v2)  ...  (uk,vk) 。 示例 1:输入: nums1 [1,7,11], nums2 [2,4,6], k 3 输出: [1,2],[1,4],[1,6] 解释: 返回序列中的前 3 对数 [1,2],[1,4],[1,6],[7,2],[7,4],[11,2],[7,6],[11,4],[11,6] 示例 2:输入: nums1 [1,1,2], nums2 [1,2,3], k 2 输出: [1,1],[1,1] 解释: 返回序列中的前 2 对数   [1,1],[1,1],[1,2],[2,1],[1,2],[2,2],[1,3],[1,3],[2,3] 提示: 1 nums1.length, nums2.length 105-109 nums1[i], nums2[i] 109nums1 和 nums2 均为 升序排列1 k 104k  nums1.length * nums2.length 解题思路 题目要求在两个有序数组 nums1 和 nums2 中找到和最小的 k 对数对 (u,v)。由于两个数组是有序的因此可以利用最小堆优先队列来高效地找到这些数对。 初始堆构建将所有可能的数对中的前 k 对数对放入最小堆中。具体来说可以只考虑 nums1 中前 k 个元素与 nums2 第一个元素的配对 (nums1[i], nums2[0]) 放入堆中因为这些是可能的最小组合。 堆操作每次从堆中取出最小的数对 (nums1[i], nums2[j])然后将其后继组合 (nums1[i], nums2[j1]) 入堆。这样可以确保找到最小的 k 个数对。 终止条件堆中取出 k 个元素后停止或者堆为空时停止。 复杂度分析 时间复杂度初始堆的构建需要 O(k log k) 时间而每次堆操作的插入和删除都是 O(log k)。由于我们最多需要进行 k 次这样的操作所以总时间复杂度为 O(k log k)。 空间复杂度堆的空间复杂度为 O(k)因此总的空间复杂度为 O(k)。 代码实现 package org.zyf.javabasic.letcode.jd150.heap;import java.util.ArrayList; import java.util.List; import java.util.PriorityQueue;/*** program: zyfboot-javabasic* description: 查找和最小的K对数字* author: zhangyanfeng* create: 2024-08-25 18:47**/ public class KSmallestPairs {public ListListInteger kSmallestPairs(int[] nums1, int[] nums2, int k) {// 最小堆用于存储数对的索引 [i, j]PriorityQueueint[] pq new PriorityQueue(k, (o1, o2) - {return nums1[o1[0]] nums2[o1[1]] - nums1[o2[0]] - nums2[o2[1]];});// 结果列表ListListInteger ans new ArrayList();int m nums1.length;int n nums2.length;// 初始化堆放入 nums1 的前 k 个元素与 nums2 第一个元素的组合for (int i 0; i Math.min(m, k); i) {pq.offer(new int[]{i, 0});}// 取出最小的 k 个数对while (k-- 0 !pq.isEmpty()) {int[] idxPair pq.poll(); // 取出最小和的数对索引ListInteger list new ArrayList();list.add(nums1[idxPair[0]]);list.add(nums2[idxPair[1]]);ans.add(list);// 若 j 1 还在数组范围内继续将 (i, j1) 放入堆中if (idxPair[1] 1 n) {pq.offer(new int[]{idxPair[0], idxPair[1] 1});}}return ans;} }124.数据流的中位数困难 题目描述 中位数是有序整数列表中的中间值。如果列表的大小是偶数则没有中间值中位数是两个中间值的平均值。 例如 arr [2,3,4] 的中位数是 3 。例如 arr [2,3] 的中位数是 (2 3) / 2 2.5 。 实现 MedianFinder 类: MedianFinder() 初始化 MedianFinder 对象。 void addNum(int num) 将数据流中的整数 num 添加到数据结构中。 double findMedian() 返回到目前为止所有元素的中位数。与实际答案相差 10-5 以内的答案将被接受。 示例 1输入 [MedianFinder, addNum, addNum, findMedian, addNum, findMedian] [[], [1], [2], [], [3], []] 输出 [null, null, null, 1.5, null, 2.0] 解释 MedianFinder medianFinder new MedianFinder(); medianFinder.addNum(1); // arr [1] medianFinder.addNum(2); // arr [1, 2] medianFinder.findMedian(); // 返回 1.5 ((1 2) / 2) medianFinder.addNum(3); // arr[1, 2, 3] medianFinder.findMedian(); // return 2.0 提示: -105  num 105在调用 findMedian 之前数据结构中至少有一个元素最多 5 * 104 次调用 addNum 和 findMedian 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第76题。 二十、位运算 125.二进制求和简单 题目描述 给你两个二进制字符串 a 和 b 以二进制字符串的形式返回它们的和。 示例 1输入:a 11, b 1 输出100 示例 2输入a 1010, b 1011 输出10101 提示 1 a.length, b.length 104a 和 b 仅由字符 0 或 1 组成字符串如果不是 0 就不含前导零 解题思路 我们要对两个二进制字符串 a 和 b 进行逐位相加并且考虑进位的问题。由于二进制只有 0 和 1逻辑相对简单。我们从两个字符串的最低位末尾开始逐位相加如果某一位的和大于等于 2则产生一个进位。继续处理更高位直到处理完所有位或者处理完进位。解题步骤: 初始化指针和进位使用两个指针 i 和 j 分别指向字符串 a 和 b 的末尾。初始化进位 carry 为 0。初始化结果字符串 StringBuilder result。 逐位相加从 i 和 j 逐位向前遍历计算当前位的和并考虑前一次的进位 carry。根据当前位的和决定是否有新的进位并将结果的当前位存储到 result 中。 处理剩余的进位如果遍历完成后仍有进位则将进位加到结果中。 返回结果由于 result 是从低位开始构建的最终需要将其反转后返回。 复杂度分析 时间复杂度O(max(m, n))其中 m 和 n 分别是字符串 a 和 b 的长度。我们最多需要遍历较长的字符串。 空间复杂度O(max(m, n))用于存储结果字符串的空间。 代码实现 package org.zyf.javabasic.letcode.jd150.binary;/*** program: zyfboot-javabasic* description: 二进制求和* author: zhangyanfeng* create: 2024-08-25 18:54**/ public class AddBinary {public String addBinary(String a, String b) {// 使用 StringBuilder 来存储结果StringBuilder result new StringBuilder();// 初始化指针 i 和 j 分别指向 a 和 b 的末尾int i a.length() - 1;int j b.length() - 1;// 初始化进位 carryint carry 0;// 遍历 a 和 bwhile (i 0 || j 0) {// 获取当前位的值如果指针已经超出字符串范围则认为当前位为 0int bitA (i 0) ? a.charAt(i) - 0 : 0;int bitB (j 0) ? b.charAt(j) - 0 : 0;// 计算当前位的和包括进位int sum bitA bitB carry;// 计算新的进位如果 sum 2 则有进位carry sum / 2;// 将 sum 的余数添加到结果中result.append(sum % 2);// 指针向前移动i--;j--;}// 如果遍历结束后仍有进位则需要添加到结果中if (carry ! 0) {result.append(carry);}// 最终需要将结果反转并返回return result.reverse().toString();} }126.颠倒二进制位简单 题目描述 颠倒给定的 32 位无符号整数的二进制位。 提示 请注意在某些语言如 Java中没有无符号整数类型。在这种情况下输入和输出都将被指定为有符号整数类型并且不应影响您的实现因为无论整数是有符号的还是无符号的其内部的二进制表示形式都是相同的。在 Java 中编译器使用二进制补码记法来表示有符号整数。因此在 示例 2 中输入表示有符号整数 -3输出表示有符号整数 -1073741825。 示例 1输入n 00000010100101000001111010011100 输出964176192 (00111001011110000010100101000000) 解释输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596 因此返回 964176192其二进制表示形式为 00111001011110000010100101000000。 示例 2输入n 11111111111111111111111111111101 输出3221225471 (10111111111111111111111111111111) 解释输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293   因此返回 3221225471 其二进制表示形式为 10111111111111111111111111111111 。 提示 输入是一个长度为 32 的二进制字符串 进阶: 如果多次调用这个函数你将如何优化你的算法 解题思路 我们要颠倒一个32位无符号整数的二进制位这意味着将最左边的位与最右边的位交换依次向中间靠拢直到完成整个二进制串的反转。可以使用位运算来完成这个任务。具体步骤如下 初始化结果初始化一个变量 result用于存储最终的反转结果。 逐位反转 我们将输入整数 n 的每一位取出并将其加入到 result 的相应位置。具体来说将 n 右移一位并将 n 的最低位取出加入到 result 的最高位通过左移操作。重复这一过程32次。 返回结果返回 result 即可得到反转后的二进制整数。 复杂度分析 时间复杂度O(1)。虽然我们需要循环32次但由于32是常数因此时间复杂度为O(1)。空间复杂度O(1)。仅使用了几个额外的变量来存储中间结果和最终结果空间复杂度为O(1)。 代码实现 package org.zyf.javabasic.letcode.jd150.binary;/*** program: zyfboot-javabasic* description: 颠倒二进制位* author: zhangyanfeng* create: 2024-08-25 18:57**/ public class ReverseBits {// 颠倒32位无符号整数的二进制位public int reverseBits(int n) {// 初始化结果为0int result 0;// 遍历32位for (int i 0; i 32; i) {// 将result左移1位为下一个反转位腾出空间result 1;// 将n的最低位加到result的最低位result | (n 1);// 将n右移1位处理下一个位n 1;}// 返回反转后的结果return result;} }127.位1的个数简单 题目描述 编写一个函数获取一个正整数的二进制形式并返回其二进制表达式中 设置位 的个数也被称为汉明重量。 示例 1输入n 11 输出3 解释输入的二进制串 1011 中共有 3 个设置位。 示例 2输入n 128 输出1 解释输入的二进制串 10000000 中共有 1 个设置位。 示例 3输入n 2147483645 输出30 解释输入的二进制串 11111111111111111111111111111101 中共有 30 个设置位。 提示 1 n 231 - 1 解题思路 可见数学思维编程练习总结_编程中的数学思维-CSDN博客中的第11题。 可见剑指offer所有编程练习总结分析_currentsum - small-CSDN博客中的第36题。 128.只出现一次的数字简单 题目描述 给你一个 非空 整数数组 nums 除了某个元素只出现一次以外其余每个元素均出现两次。找出那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法来解决此问题且该算法只使用常量额外空间。 示例 1 输入nums [2,2,1] 输出1 示例 2 输入nums [4,1,2,1,2] 输出4 示例 3 输入nums [1] 输出1 提示 1 nums.length 3 * 104-3 * 104 nums[i] 3 * 104除了某个元素只出现一次以外其余每个元素均出现两次。 解题思路 可见LeetCode 精选 75 回顾-CSDN博客中的第68题。 129.只出现一次的数字 II中等 题目描述 给你一个整数数组 nums 除某个元素仅出现 一次 外其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法且使用常数级空间来解决此问题。 示例 1输入nums [2,2,3,2] 输出3 示例 2输入nums [0,1,0,1,0,1,99] 输出99 提示 1 nums.length 3 * 104-231 nums[i] 231 - 1nums 中除某个元素仅出现 一次 外其余每个元素都恰出现 三次 解题思路 对于一个整数如果我们考虑它的每一位0或1那么对于出现了三次的元素来说某一位上的1出现的次数必然是3的倍数。而对于仅出现一次的元素来说其某些位上的1不会是3的倍数。因此我们可以通过统计数组中每一位上1出现的次数并对3取模结果就是仅出现一次的元素在该位上的值。具体步骤 初始化两个变量 ones 和 twos 用于记录位的状态。其中 ones 表示在当前位出现了1次的数。twos 表示在当前位出现了2次的数。 对数组中的每个数进行如下操作更新 ones 和 twos 的值考虑当前位是否被当前数占用。 最终ones 的值即为仅出现一次的元素。 复杂度分析 时间复杂度O(n)其中 n 是数组的长度。我们仅需遍历数组一次。空间复杂度O(1)只使用了常数个额外变量。 代码实现 package org.zyf.javabasic.letcode.jd150.binary;/*** program: zyfboot-javabasic* description: 只出现一次的数字 II* author: zhangyanfeng* create: 2024-08-25 19:05**/ public class SingleNumber {public int singleNumber(int[] nums) {// ones记录出现1次的位twos记录出现2次的位int ones 0, twos 0;for (int num : nums) {// 更新onestwos的值ones (ones ^ num) ~twos;twos (twos ^ num) ~ones;}// 返回ones表示那个只出现一次的数return ones;} }130.数字范围按位与中等 题目描述 给你两个整数 left 和 right 表示区间 [left, right] 返回此区间内所有数字 按位与 的结果包含 left 、right 端点。 示例 1输入left 5, right 7 输出4 示例 2输入left 0, right 0 输出0 示例 3输入left 1, right 2147483647 输出0 提示 0 left right 231 - 1 解题思路 对于给定的区间 [left,right]要求返回区间内所有数字的按位与的结果。按位与操作的特性决定了如果区间范围较大那么结果将会受到范围内的低位影响。我们可以通过找到 left 和 right 的公共前缀来减少计算的复杂度。 关键观察如果我们对连续的数字进行按位与操作那么每次操作可能都会清除低位上的1。最终结果取决于 left 和 right 在高位的公共前缀。 具体做法将 left 和 right 一直右移直到它们相等。记录右移的次数 shift最终将相等的值左移回原位置即为结果。 具体步骤 初始化 shift 为0用来记录右移的次数。不断右移 left 和 right直到 left right。将最终相等的 left 左移 shift 次即为最终结果。 复杂度分析 时间复杂度O(log⁡(right))因为我们在计算过程中将数字右移最多右移 O(log⁡(right))次。空间复杂度O(1)只使用了常数个额外变量。 代码实现 package org.zyf.javabasic.letcode.jd150.binary;/*** program: zyfboot-javabasic* description: 数字范围按位与* author: zhangyanfeng* create: 2024-08-25 19:11**/ public class RangeBitwiseAnd {public int rangeBitwiseAnd(int left, int right) {int shift 0;// 不断右移left和right直到它们相等while (left right) {left 1;right 1;shift;}// 将相等的left左移回原位置return left shift;} }二十一、数学 131.回文数简单 题目描述 给你一个整数 x 如果 x 是一个回文整数返回 true 否则返回 false 。 回文数是指正序从左向右和倒序从右向左读都是一样的整数。 例如121 是回文而 123 不是。 示例 1输入x 121 输出true 示例 2输入x -121 输出false 解释从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。 示例 3输入x 10 输出false 解释从右向左读, 为 01 。因此它不是一个回文数。 提示 -231  x 231 - 1 进阶你能不将整数转为字符串来解决这个问题吗 解题思路 具体可见数学思维编程练习总结_编程中的数学思维-CSDN博客中的第6题。 132.加一简单 题目描述 给定一个由 整数 组成的 非空 数组所表示的非负整数在该数的基础上加一。 最高位数字存放在数组的首位 数组中每个元素只存储单个数字。 你可以假设除了整数 0 之外这个整数不会以零开头。 示例 1输入digits [1,2,3] 输出[1,2,4] 解释输入数组表示数字 123。 示例 2输入digits [4,3,2,1] 输出[4,3,2,2] 解释输入数组表示数字 4321。 示例 3输入digits [0] 输出[1] 提示 1 digits.length 1000 digits[i] 9 解题思路 具体可见数组知识及编程练习总结-CSDN博客中的第8题。 133. 阶乘后的零中等 题目描述给定一个整数 n 返回 n! 结果中尾随零的数量。 提示 n! n * (n - 1) * (n - 2) * ... * 3 * 2 * 1 示例 1输入n 3 输出0 解释3! 6 不含尾随 0 示例 2输入n 5 输出1 解释5! 120 有一个尾随 0 示例 3输入n 0 输出0 提示 0 n 104 进阶你可以设计并实现对数时间复杂度的算法来解决此问题吗 解题思路 具体可见数学思维编程练习总结_编程中的数学思维-CSDN博客中的第20题。 134.x 的平方根 中等 题目描述 给你一个非负整数 x 计算并返回 x 的 算术平方根 。 由于返回类型是整数结果只保留 整数部分 小数部分将被 舍去 。 注意不允许使用任何内置指数函数和算符例如 pow(x, 0.5) 或者 x ** 0.5 。 示例 1输入x 4 输出2 示例 2输入x 8 输出2 解释8 的算术平方根是 2.82842..., 由于返回类型是整数小数部分将被舍去。 提示 0 x 231 - 1 解题思路 具体可见数学思维编程练习总结_编程中的数学思维-CSDN博客中的第22题。 135.Pow(x, n)中等 题目描述 实现 pow(x, n) 即计算 x 的整数 n 次幂函数即xn 。 示例 1输入x 2.00000, n 10 输出1024.00000 示例 2输入x 2.10000, n 3 输出9.26100 示例 3输入x 2.00000, n -2 输出0.25000 解释2-2 1/22 1/4 0.25 提示 -100.0 x 100.0-231 n 231-1n 是一个整数要么 x 不为零要么 n 0 。-104 xn 104 解题思路 具体可见数学思维编程练习总结_编程中的数学思维-CSDN博客中的第23题。 136.直线上最多的点数困难 题目描述 给你一个数组 points 其中 points[i] [xi, yi] 表示 X-Y 平面上的一个点。求最多有多少个点在同一条直线上。 示例 1 输入points [[1,1],[2,2],[3,3]] 输出3示例 2 输入points [[1,1],[3,2],[5,3],[4,1],[2,3],[1,4]] 输出4提示 1 points.length 300points[i].length 2-104 xi, yi 104points 中的所有点 互不相同 解题思路 要找出在同一条直线上的最多点数可以利用斜率的概念。具体思路如下 固定一个点作为基准点对于每个点 i计算它与其他点 jj ≠ i之间的斜率。斜率相同的点必然在同一条直线上。 使用哈希表统计斜率用一个哈希表来记录当前基准点 i 与其他点 j 之间的斜率出现的次数。相同斜率的点数越多说明这些点与基准点在同一条直线上。 特殊情况处理 重合点如果两个点坐标完全相同需要单独计数。垂直线当两点的 x 坐标相同斜率为无穷大此时用特定值来表示这种情况。 最大点数计算对于每个基准点找到斜率最多的那一组点加上基准点本身以及任何重合的点数就可以得到以该点为基准的最大点数。最终的结果是所有基准点下的最大值。 复杂度分析 时间复杂度O(n^2)其中 n 是点的数量。每个点作为基准点时都要计算与其他 n-1 个点的斜率。空间复杂度O(n)用于存储斜率的哈希表。 代码实现 package org.zyf.javabasic.letcode.jd150.binary;import java.util.HashMap; import java.util.Map;/*** program: zyfboot-javabasic* description: 直线上最多的点数* author: zhangyanfeng* create: 2024-08-25 19:43**/ public class MaxPoints {public int maxPoints(int[][] points) {int n points.length;if (n 3) return n;int maxPointsOnLine 1;for (int i 0; i n; i) {MapString, Integer slopeMap new HashMap();int duplicate 0; // 记录与基准点重合的点数int maxForCurrentPoint 0;for (int j i 1; j n; j) {int deltaX points[j][0] - points[i][0];int deltaY points[j][1] - points[i][1];if (deltaX 0 deltaY 0) {// 基准点与某点重合duplicate;continue;}// 化简分数形式的斜率int gcd gcd(deltaX, deltaY);deltaX / gcd;deltaY / gcd;// 确保斜率的唯一性处理垂直和水平的情况String slope deltaX / deltaY;slopeMap.put(slope, slopeMap.getOrDefault(slope, 0) 1);maxForCurrentPoint Math.max(maxForCurrentPoint, slopeMap.get(slope));}// 计算基于当前基准点的最大点数maxPointsOnLine Math.max(maxPointsOnLine, maxForCurrentPoint duplicate 1);}return maxPointsOnLine;}// 计算最大公约数private int gcd(int a, int b) {return b 0 ? a : gcd(b, a % b);} }二十二、一维动态规划 137.爬楼梯简单 题目描述 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢 示例 1输入n 2 输出2 解释有两种方法可以爬到楼顶。 1. 1 阶 1 阶 2. 2 阶 示例 2输入n 3 输出3 解释有三种方法可以爬到楼顶。 1. 1 阶 1 阶 1 阶 2. 1 阶 2 阶 3. 2 阶 1 阶 提示 1 n 45 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第81题。 138.打家劫舍中等 题目描述 你是一个专业的小偷计划偷窃沿街的房屋。每间房内都藏有一定的现金影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统如果两间相邻的房屋在同一晚上被小偷闯入系统会自动报警。 给定一个代表每个房屋存放金额的非负整数数组计算你 不触动警报装置的情况下 一夜之内能够偷窃到的最高金额。 示例 1输入[1,2,3,1] 输出4 解释偷窃 1 号房屋 (金额 1) 然后偷窃 3 号房屋 (金额 3)。   偷窃到的最高金额 1 3 4 。 示例 2输入[2,7,9,3,1] 输出12 解释偷窃 1 号房屋 (金额 2), 偷窃 3 号房屋 (金额 9)接着偷窃 5 号房屋 (金额 1)。   偷窃到的最高金额 2 9 1 12 。 提示 1 nums.length 1000 nums[i] 400 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第83题。 139.单词拆分 中等 题目描述 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true。 注意不要求字典中出现的单词全部都使用并且字典中的单词可以重复使用。 示例 1输入: s leetcode, wordDict [leet, code] 输出: true 解释: 返回 true 因为 leetcode 可以由 leet 和 code 拼接成。 示例 2输入: s applepenapple, wordDict [apple, pen] 输出: true 解释: 返回 true 因为 applepenapple 可以由 apple pen apple 拼接成。   注意你可以重复使用字典中的单词。 示例 3输入: s catsandog, wordDict [cats, dog, sand, and, cat] 输出: false 提示 1 s.length 3001 wordDict.length 10001 wordDict[i].length 20s 和 wordDict[i] 仅由小写英文字母组成wordDict 中的所有字符串 互不相同 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第86题。 140.零钱兑换中等 题目描述 给你一个整数数组 coins 表示不同面额的硬币以及一个整数 amount 表示总金额。 计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额返回 -1 。 你可以认为每种硬币的数量是无限的。 示例 1输入coins  [1, 2, 5], amount  11 输出3 解释11 5 5 1 示例 2输入coins  [2], amount  3 输出-1 示例 3输入coins [1], amount 0 输出0 提示 1 coins.length 121 coins[i] 231 - 10 amount 104 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第85题。 141.最长递增子序列  中等 题目描述 给你一个整数数组 nums 找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列删除或不删除数组中的元素而不改变其余元素的顺序。例如[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。  示例 1输入nums [10,9,2,5,3,7,101,18] 输出4 解释最长递增子序列是 [2,3,7,101]因此长度为 4 。 示例 2输入nums [0,1,0,3,2,3] 输出4 示例 3输入nums [7,7,7,7,7,7,7] 输出1 提示 1 nums.length 2500-104 nums[i] 104 进阶你能将算法的时间复杂度降低到 O(n log(n)) 吗? 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第87题。 二十三、多维动态规划 142.三角形最小路径和中等 题目描述 给定一个三角形 triangle 找出自顶向下的最小路径和。 每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 1 的两个结点。也就是说如果正位于当前行的下标 i 那么下一步可以移动到下一行的下标 i 或 i 1 。 示例 1 输入triangle [[2],[3,4],[6,5,7],[4,1,8,3]] 输出11 解释如下面简图所示23 46 5 7 4 1 8 3 自顶向下的最小路径和为 11即2  3  5  1  11。示例 2输入triangle [[-10]] 输出-10 提示 1 triangle.length 200triangle[0].length 1triangle[i].length triangle[i - 1].length 1-104 triangle[i][j] 104 进阶 你可以只使用 O(n) 的额外空间n 为三角形的总行数来解决这个问题吗 解题思路 要解决三角形最小路径和的问题可以采用动态规划Dynamic Programming的方法。该方法能够有效地在三角形结构中计算自顶向下的最小路径和。 自底向上动态规划 我们可以从三角形的底部开始逐行向上计算每个元素的最小路径和。对于每个元素 triangle[i][j]其最小路径和可以通过其下一行的两个相邻元素 triangle[i1][j] 和 triangle[i1][j1] 的最小值来确定。状态转移方程dp[i][j] triangle[i][j] min(dp[i1][j], dp[i1][j1])。 空间优化 我们可以直接在原三角形数组上进行修改使得最终顶部元素保存的是从顶到底的最小路径和。这样空间复杂度可以降到 O(n)其中 n 是三角形的行数。 复杂度分析 时间复杂度O(n^2)其中 n 是三角形的行数。我们需要遍历整个三角形的每个元素一次。空间复杂度O(1)我们直接在原数组上进行操作不需要额外的空间。 代码实现 package org.zyf.javabasic.letcode.jd150.dynamic;import java.util.List;/*** program: zyfboot-javabasic* description: 三角形最小路径和* author: zhangyanfeng* create: 2024-08-25 19:53**/ public class MinimumTotal {public int minimumTotal(ListListInteger triangle) {int n triangle.size();// 从倒数第二行开始自底向上计算最小路径和for (int i n - 2; i 0; i--) {for (int j 0; j i; j) {// 当前元素加上下一行的两个相邻元素的最小值triangle.get(i).set(j, triangle.get(i).get(j) Math.min(triangle.get(i 1).get(j), triangle.get(i 1).get(j 1)));}}// 最顶端元素即为最小路径和return triangle.get(0).get(0);} }143.最小路径和中等 题目描述 给定一个包含非负整数的 m x n 网格 grid 请找出一条从左上角到右下角的路径使得路径上的数字总和为最小。 说明每次只能向下或者向右移动一步。 示例 1 输入grid [[1,3,1],[1,5,1],[4,2,1]] 输出7 解释因为路径 1→3→1→1→1 的总和最小。示例 2输入grid [[1,2,3],[4,5,6]] 输出12 提示 m grid.lengthn grid[i].length1 m, n 2000 grid[i][j] 200 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第92题。 144.不同路径 II中等 题目描述 一个机器人位于一个 m x n 网格的左上角 起始点在下图中标记为 “Start” 。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角在下图中标记为 “Finish”。 现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径 网格中的障碍物和空位置分别用 1 和 0 来表示。 示例 1 输入obstacleGrid [[0,0,0],[0,1,0],[0,0,0]] 输出2 解释3x3 网格的正中间有一个障碍物。 从左上角到右下角一共有 2 条不同的路径 1. 向右 - 向右 - 向下 - 向下 2. 向下 - 向下 - 向右 - 向右示例 2 输入obstacleGrid [[0,1],[0,0]] 输出1提示 m  obstacleGrid.lengthn  obstacleGrid[i].length1 m, n 100obstacleGrid[i][j] 为 0 或 1 解题思路 要计算从网格的左上角到右下角的所有不同路径数量考虑网格中可能存在的障碍物我们可以使用动态规划Dynamic Programming来解决这个问题: 动态规划表格使用一个二维数组 dp其中 dp[i][j] 表示从 (0,0) 到 (i,j) 的不同路径数量。 初始化dp[0][0] 初始化为 1如果起点 (0,0) 处有障碍物则 dp[0][0] 为 0因为机器人不能从起点开始。如果网格的第一行或第一列有障碍物后续的路径数也应该为 0。 状态转移对于每个位置 (i,j)如果 obstacleGrid[i][j] 为 0无障碍物那么 dp[i][j] 可以由上方 (i-1,j) 或左方 (i,j-1) 位置的路径数得出dp[i][j] (i 0 ? dp[i-1][j] : 0) (j 0 ? dp[i][j-1] : 0);如果 obstacleGrid[i][j] 为 1有障碍物则 dp[i][j] 为 0。 结果最终的结果是 dp[m-1][n-1]即网格右下角的路径数量。 复杂度分析 时间复杂度O(m * n)我们需要遍历整个网格。空间复杂度O(m * n)需要额外的二维数组来存储路径数量。 代码实现 package org.zyf.javabasic.letcode.jd150.dynamic;/*** program: zyfboot-javabasic* description: 不同路径 II* author: zhangyanfeng* create: 2024-08-25 20:00**/ public class UniquePathsWithObstacles {public int uniquePathsWithObstacles(int[][] obstacleGrid) {int m obstacleGrid.length; // 行数int n obstacleGrid[0].length; // 列数// 创建一个二维数组 dp用于存储从 (0,0) 到 (i,j) 的路径数量int[][] dp new int[m][n];// 初始化 dp 数组// 如果起点有障碍物则直接返回 0if (obstacleGrid[0][0] 1) {return 0;}dp[0][0] 1; // 起点到起点的路径数为 1// 填充第一行for (int j 1; j n; j) {dp[0][j] (obstacleGrid[0][j] 0 dp[0][j-1] 1) ? 1 : 0;}// 填充第一列for (int i 1; i m; i) {dp[i][0] (obstacleGrid[i][0] 0 dp[i-1][0] 1) ? 1 : 0;}// 填充其余的 dp 数组for (int i 1; i m; i) {for (int j 1; j n; j) {if (obstacleGrid[i][j] 0) {dp[i][j] dp[i-1][j] dp[i][j-1];} else {dp[i][j] 0; // 如果有障碍物路径数为 0}}}// 返回右下角的路径数量return dp[m-1][n-1];} }145.最长回文子串中等 题目描述 给你一个字符串 s找到 s 中最长的 回文子串。 示例 1输入s babad 输出bab 解释aba 同样是符合题意的答案。 示例 2输入s cbbd 输出bb 提示 1 s.length 1000s 仅由数字和英文字母组成 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第93题。 146.交错字符串中等 题目描述 给定三个字符串 s1、s2、s3请你帮忙验证 s3 是否是由 s1 和 s2 交错 组成的。 两个字符串 s 和 t 交错 的定义与过程如下其中每个字符串都会被分割成若干 非空 子字符串 s s1 s2 ... snt t1 t2 ... tm|n - m| 1交错 是 s1 t1 s2 t2 s3 t3 ... 或者 t1 s1 t2 s2 t3 s3 ... 注意a b 意味着字符串 a 和 b 连接。 示例 1 输入s1 aabcc, s2 dbbca, s3 aadbbcbcac 输出true示例 2输入s1 aabcc, s2 dbbca, s3 aadbbbaccc 输出false 示例 3输入s1 , s2 , s3 输出true 提示 0 s1.length, s2.length 1000 s3.length 200s1、s2、和 s3 都由小写英文字母组成 进阶您能否仅使用 O(s2.length) 额外的内存空间来解决它? 解题思路 要验证字符串 s3 是否是由 s1 和 s2 交错组成的可以使用动态规划Dynamic Programming, DP来实现。我们将定义一个二维数组 dp 来表示从 s1 和 s2 生成 s3 的可能性。解题思路 定义状态使用一个二维布尔数组 dp其中 dp[i][j] 表示 s3 的前 i j 个字符是否可以由 s1 的前 i 个字符和 s2 的前 j 个字符交错组成。 初始化 dp[0][0] true因为空字符串可以由两个空字符串交错组成。对于第一行即 i 0dp[0][j] 取决于 s2 是否可以匹配 s3 的前 j 个字符。对于第一列即 j 0dp[i][0] 取决于 s1 是否可以匹配 s3 的前 i 个字符。 状态转移 对于每个位置 (i, j)如果 dp[i][j] 为 true则如果 j len(s2) 且 s2[j] 等于 s3[i j]则 dp[i][j 1] 应该为 true。如果 i len(s1) 且 s1[i] 等于 s3[i j]则 dp[i 1][j] 应该为 true。 结果最终结果是 dp[len(s1)][len(s2)]即 s1 和 s2 是否能交错组成 s3。 复杂度分析 时间复杂度O(m * n)其中 m 和 n 分别是 s1 和 s2 的长度。需要遍历 dp 数组的每一个位置。空间复杂度O(m * n)需要额外的二维数组 dp 来存储中间结果。 代码实现 package org.zyf.javabasic.letcode.jd150.dynamic;/*** program: zyfboot-javabasic* description: 交错字符串* author: zhangyanfeng* create: 2024-08-25 20:05**/ public class Interleave {public boolean isInterleave(String s1, String s2, String s3) {int m s1.length();int n s2.length();int l s3.length();// 如果 s1 和 s2 的长度之和不等于 s3 的长度则不能交错组成 s3if (m n ! l) {return false;}// 创建 dp 数组boolean[][] dp new boolean[m 1][n 1];// 初始化 dp 数组dp[0][0] true;// 初始化第一行for (int j 1; j n; j) {dp[0][j] dp[0][j - 1] s2.charAt(j - 1) s3.charAt(j - 1);}// 初始化第一列for (int i 1; i m; i) {dp[i][0] dp[i - 1][0] s1.charAt(i - 1) s3.charAt(i - 1);}// 填充 dp 数组for (int i 1; i m; i) {for (int j 1; j n; j) {dp[i][j] (dp[i - 1][j] s1.charAt(i - 1) s3.charAt(i j - 1)) ||(dp[i][j - 1] s2.charAt(j - 1) s3.charAt(i j - 1));}}// 返回结果return dp[m][n];} }147.编辑距离中等 题目描述 给你两个单词 word1 和 word2 请返回将 word1 转换成 word2 所使用的最少操作数  。 你可以对一个单词进行如下三种操作 插入一个字符删除一个字符替换一个字符 示例 1输入word1 horse, word2 ros 输出3 解释 horse - rorse (将 h 替换为 r) rorse - rose (删除 r) rose - ros (删除 e) 示例 2输入word1 intention, word2 execution 输出5 解释 intention - inention (删除 t) inention - enention (将 i 替换为 e) enention - exention (将 n 替换为 x) exention - exection (将 n 替换为 c) exection - execution (插入 u) 提示 0 word1.length, word2.length 500word1 和 word2 由小写英文字母组成 解题思路 具体可见LeetCode 热题 100 回顾-CSDN博客中的第95题。 148.买卖股票的最佳时机 III困难 题目描述 给定一个数组它的第 i 个元素是一支给定的股票在第 i 天的价格。 设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。 注意你不能同时参与多笔交易你必须在再次购买前出售掉之前的股票。 示例 1:输入prices [3,3,5,0,0,3,1,4] 输出6 解释在第 4 天股票价格 0的时候买入在第 6 天股票价格 3的时候卖出这笔交易所能获得利润 3-0 3 。   随后在第 7 天股票价格 1的时候买入在第 8 天 股票价格 4的时候卖出这笔交易所能获得利润 4-1 3 。 示例 2输入prices [1,2,3,4,5] 输出4 解释在第 1 天股票价格 1的时候买入在第 5 天 股票价格 5的时候卖出, 这笔交易所能获得利润 5-1 4 。     注意你不能在第 1 天和第 2 天接连购买股票之后再将它们卖出。     因为这样属于同时参与了多笔交易你必须在再次购买前出售掉之前的股票。 示例 3输入prices [7,6,4,3,1] 输出0 解释在这个情况下, 没有交易完成, 所以最大利润为 0。 示例 4输入prices [1] 输出0 提示 1  prices.length 1050  prices[i]  105 解题思路 要在数组 prices 中计算最多可以完成两笔交易的最大利润我们可以使用动态规划来解决这个问题。具体地我们可以将问题拆分为两个子问题进行第一次交易和进行第二次交易。 我们定义四个变量 buy1第一次购买股票时的最大利润。sell1第一次出售股票时的最大利润。buy2第二次购买股票时的最大利润。sell2第二次出售股票时的最大利润。 动态规划转移方程 复杂度分析 时间复杂度O(n)因为我们只需要遍历一次 prices 数组。空间复杂度O(1)因为我们只使用了常数个额外变量。 代码实现 package org.zyf.javabasic.letcode.jd150.dynamic;/*** program: zyfboot-javabasic* description: 买卖股票的最佳时机 III* author: zhangyanfeng* create: 2024-08-25 20:11**/ public class MaxProfit {public int maxProfit(int[] prices) {int n prices.length; // 获取价格数组的长度// 初始化动态规划状态变量int buy1 -prices[0], sell1 0; // 第一次购买和出售的初始状态int buy2 -prices[0], sell2 0; // 第二次购买和出售的初始状态// 遍历每一天的价格for (int i 1; i n; i) {// 更新第一次购买的最大利润buy1 Math.max(buy1, -prices[i]);// 更新第一次出售的最大利润sell1 Math.max(sell1, buy1 prices[i]);// 更新第二次购买的最大利润buy2 Math.max(buy2, sell1 - prices[i]);// 更新第二次出售的最大利润sell2 Math.max(sell2, buy2 prices[i]);}// 返回最多完成两笔交易的最大利润return sell2;} }149.买卖股票的最佳时机 IV困难 题目描述 给你一个整数数组 prices 和一个整数 k 其中 prices[i] 是某支给定的股票在第 i 天的价格。 设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。也就是说你最多可以买 k 次卖 k 次。 注意你不能同时参与多笔交易你必须在再次购买前出售掉之前的股票。 示例 1输入k 2, prices [2,4,1] 输出2 解释在第 1 天 (股票价格 2) 的时候买入在第 2 天 (股票价格 4) 的时候卖出这笔交易所能获得利润 4-2 2 。 示例 2输入k 2, prices [3,2,6,5,0,3] 输出7 解释在第 2 天 (股票价格 2) 的时候买入在第 3 天 (股票价格 6) 的时候卖出, 这笔交易所能获得利润 6-2 4 。 随后在第 5 天 (股票价格 0) 的时候买入在第 6 天 (股票价格 3) 的时候卖出, 这笔交易所能获得利润 3-0 3 。 提示 1 k 1001 prices.length 10000 prices[i] 1000 解题思路 对于这个问题我们可以使用动态规划来求解。主要思路是构建一个二维动态规划表其中 dp[t][d] 表示在第 d 天完成 t 次交易的最大利润。 动态规划状态定义 dp[t][d]在第 d 天完成 t 次交易的最大利润。dp[t][d] 需要利用前一天的状态和当天的价格来更新。 动态规划转移方程 复杂度分析 时间复杂度O(k * n^2)其中 n 是价格数组的长度。因为我们在每次交易中需要遍历所有之前的价格导致时间复杂度为 O(n^2)而总共要处理 k 次交易。空间复杂度O(k * n)因为我们需要一个 k x n 的 DP 表。 代码实现 package org.zyf.javabasic.letcode.jd150.dynamic;/*** program: zyfboot-javabasic* description: 买卖股票的最佳时机 IV* author: zhangyanfeng* create: 2024-08-25 20:14**/ public class MaxProfit2 {public int maxProfit(int k, int[] prices) {int n prices.length;if (n 0) return 0;// 如果交易次数 k 大于等于天数的一半意味着可以进行无限次交易if (k n / 2) {int maxProfit 0;for (int i 1; i n; i) {if (prices[i] prices[i - 1]) {maxProfit prices[i] - prices[i - 1];}}return maxProfit;}// dp[t][d] 表示第 d 天完成 t 次交易的最大利润int[][] dp new int[k 1][n];// 遍历每次交易for (int t 1; t k; t) {// 在第 t 次交易时初始化最优利润int maxDiff -prices[0];// 遍历每一天for (int d 1; d n; d) {// 更新 dp[t][d]考虑不进行交易和进行交易两种情况dp[t][d] Math.max(dp[t][d - 1], prices[d] maxDiff);// 更新 maxDiff为下一天的交易准备maxDiff Math.max(maxDiff, dp[t - 1][d] - prices[d]);}}// 返回最多完成 k 次交易的最大利润return dp[k][n - 1];} }150.最大正方形中等 题目描述 在一个由 0 和 1 组成的二维矩阵内找到只包含 1 的最大正方形并返回其面积。 示例 1 输入matrix [[1,0,1,0,0],[1,0,1,1,1],[1,1,1,1,1],[1,0,0,1,0]] 输出4示例 2 输入matrix [[0,1],[1,0]] 输出1示例 3输入matrix [[0]] 输出0 提示 m matrix.lengthn matrix[i].length1 m, n 300matrix[i][j] 为 0 或 1 解题思路 复杂度分析 时间复杂度O(m * n)其中 m 是矩阵的行数n 是矩阵的列数。我们需要遍历每一个矩阵位置一次。空间复杂度O(m * n)需要额外的空间来存储 dp 表。 代码实现 package org.zyf.javabasic.letcode.jd150.dynamic;/*** program: zyfboot-javabasic* description: 最大正方形* author: zhangyanfeng* create: 2024-08-25 20:18**/ public class MaximalSquare {public int maximalSquare(char[][] matrix) {int m matrix.length;if (m 0) return 0;int n matrix[0].length;// dp[i][j] 表示以 matrix[i][j] 为右下角的最大正方形的边长int[][] dp new int[m][n];int maxSide 0; // 记录最大正方形的边长// 遍历矩阵for (int i 0; i m; i) {for (int j 0; j n; j) {// 只有 matrix[i][j] 为 1 时才有可能形成正方形if (matrix[i][j] 1) {// 如果在第一行或第一列dp[i][j] 只能为 1if (i 0 || j 0) {dp[i][j] 1;} else {// 更新 dp[i][j] 的值dp[i][j] Math.min(Math.min(dp[i-1][j], dp[i][j-1]), dp[i-1][j-1]) 1;}// 更新最大边长maxSide Math.max(maxSide, dp[i][j]);}}}// 返回最大正方形的面积return maxSide * maxSide;} }
http://www.tj-hxxt.cn/news/141723.html

相关文章:

  • 什么网站都可以进入的浏览器网站制作的动画怎么做的
  • 电商网站规划昆明网站建设推广公司哪家好
  • 百度建设网站的目的5000元可注册劳务公司吗
  • 河北省承德市建设局网站上不去有哪些做二手的网站
  • 4免费网站建站网站搭建好有什么内容可以修改
  • 连锁酒店设计网站建设国外网站做调查
  • 淘宝客网站推广工具欧米伽男士手表官方网站
  • 那个企业网站是用vue做的wordpress 子网站
  • 做响应式网站制作云南城市建设培训中心网站
  • 网站建设wap站中国文化网站建设策划书
  • 2网站制作网站建设朝阳
  • 用服务器建立网站信誉好的公司官网建设
  • 企业档案网站建设wordpress 基于 网店
  • 怎么修改php网站时事热点新闻
  • 用wordpress搭建的网站哈尔滨网络招聘
  • 2023网站推荐网站建设捌金手指花总五
  • 新闻资讯网站模板下载手游网站怎么做的
  • 通化好的网站建设的公司spring mvc 做网站
  • 建设银行河北省分行网站杭州协会网站建设方案
  • 昆明网站建设有限公司深圳工程项目
  • 做网站时为什么导航时两行字wordpress菜单对齐修改
  • 做淘口令的网站网络规划设计师待遇怎么样
  • 聊城网站制作价格seo搜索引擎优化知乎
  • 北京电商网站开发公司合肥企业建网站
  • 三墩网站建设株洲市区网站建设公司
  • 杭州网站推广网站建设预算计算方法
  • 1g内存的服务器可以建设几个网站域名邮箱企业邮箱
  • 上海网站建设推荐秒搜科技十大免费开发平台app
  • 化工企业常用推广网站界面设计是什么
  • 交友免费的网站建设网站搜索出来有图片