diff --git "a/notes/Leetcode \351\242\230\350\247\243 - \344\272\214\345\210\206\346\237\245\346\211\276.md" "b/notes/Leetcode \351\242\230\350\247\243 - \344\272\214\345\210\206\346\237\245\346\211\276.md" index 32a7f35ed..0f48f06d2 100644 --- "a/notes/Leetcode \351\242\230\350\247\243 - \344\272\214\345\210\206\346\237\245\346\211\276.md" +++ "b/notes/Leetcode \351\242\230\350\247\243 - \344\272\214\345\210\206\346\237\245\346\211\276.md" @@ -7,6 +7,7 @@ * [4. 第一个错误的版本](#4-第一个错误的版本) * [5. 旋转数组的最小数字](#5-旋转数组的最小数字) * [6. 查找区间](#6-查找区间) + * [7. 两个有序数组的中位数](#7-两个有序数组的中位数) @@ -314,3 +315,42 @@ nums = [2,2], target = 2 ``` 如果 h 的取值为 nums.length - 1,那么 last = findFirst(nums, target + 1) - 1 = 1 - 1 = 0。这是因为 findFirst 只会返回 [0, nums.length - 1] 范围的值,对于 findFirst([2,2], 3) ,我们希望返回 3 应该插入 nums 中的位置,也就是数组最后一个位置再往后一个位置,即 nums.length。所以我们需要将 h 取值为 nums.length,从而使得 findFirst 返回的区间更大,能够覆盖 target 大于 nums 最后一个元素的情况。 + +## 7. 两个有序数组的中位数 + +4\. Median of Two Sorted Arrays (Hard) + +[Leetcode](https://leetcode.com/problems/median-of-two-sorted-arrays/) / [力扣](https://leetcode.cn/problems/median-of-two-sorted-arrays/) + +题目描述:求两个有序数组合并后的中位数,要求时间复杂度为 O(log(m + n))。 + +把问题转化为求两个数组合并后第 k 小的数。比较两个数组各自的第 k / 2 个元素,较小的那个元素以及它前面的元素都不可能是第 k 小,可以一次排除,然后在剩下的部分里寻找第 k - 排除个数 小的数。k 每轮减半,时间复杂度为 O(log(m + n))。 + +```java +public double findMedianSortedArrays(int[] nums1, int[] nums2) { + int total = nums1.length + nums2.length; + if (total % 2 == 1) { + return kth(nums1, 0, nums2, 0, total / 2 + 1); + } + return (kth(nums1, 0, nums2, 0, total / 2) + kth(nums1, 0, nums2, 0, total / 2 + 1)) / 2.0; +} + +// nums1[i..] 和 nums2[j..] 合并后第 k 小的数,k 从 1 开始 +private int kth(int[] nums1, int i, int[] nums2, int j, int k) { + if (i == nums1.length) { + return nums2[j + k - 1]; + } + if (j == nums2.length) { + return nums1[i + k - 1]; + } + if (k == 1) { + return Math.min(nums1[i], nums2[j]); + } + int a = Math.min(i + k / 2, nums1.length) - 1; + int b = Math.min(j + k / 2, nums2.length) - 1; + if (nums1[a] <= nums2[b]) { + return kth(nums1, a + 1, nums2, j, k - (a - i + 1)); + } + return kth(nums1, i, nums2, b + 1, k - (b - j + 1)); +} +``` diff --git "a/notes/Leetcode \351\242\230\350\247\243 - \345\212\250\346\200\201\350\247\204\345\210\222.md" "b/notes/Leetcode \351\242\230\350\247\243 - \345\212\250\346\200\201\350\247\204\345\210\222.md" index 9d73c022f..a26d35ef5 100644 --- "a/notes/Leetcode \351\242\230\350\247\243 - \345\212\250\346\200\201\350\247\204\345\210\222.md" +++ "b/notes/Leetcode \351\242\230\350\247\243 - \345\212\250\346\200\201\350\247\204\345\210\222.md" @@ -10,9 +10,11 @@ * [矩阵路径](#矩阵路径) * [1. 矩阵的最小路径和](#1-矩阵的最小路径和) * [2. 矩阵的总路径数](#2-矩阵的总路径数) + * [3. 最大正方形](#3-最大正方形) * [数组区间](#数组区间) * [1. 数组区间和](#1-数组区间和) * [2. 数组中等差递增子区间的个数](#2-数组中等差递增子区间的个数) + * [3. 最长有效括号](#3-最长有效括号) * [分割整数](#分割整数) * [1. 分割整数的最大乘积](#1-分割整数的最大乘积) * [2. 按平方数来分割整数](#2-按平方数来分割整数) @@ -241,6 +243,40 @@ public int uniquePaths(int m, int n) { } ``` +### 3. 最大正方形 + +221\. Maximal Square (Medium) + +[Leetcode](https://leetcode.com/problems/maximal-square/) / [力扣](https://leetcode.cn/problems/maximal-square/) + +题目描述:在由 0 和 1 组成的矩阵中,找出只包含 1 的最大正方形的面积。 + +dp[i][j] 表示以 (i, j) 为右下角、只包含 1 的最大正方形的边长。它的边长受左边、上边和左上角三个方向的限制,取三者的最小值再加 1: + +```text +dp[i][j] = min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1 + +matrix: 1 1 1 dp: 1 1 1 + 1 1 1 1 2 2 + 1 1 1 1 2 3 +``` + +```java +public int maximalSquare(char[][] matrix) { + int m = matrix.length, n = matrix[0].length, side = 0; + int[][] dp = new int[m + 1][n + 1]; + for (int i = 1; i <= m; i++) { + for (int j = 1; j <= n; j++) { + if (matrix[i - 1][j - 1] == '1') { + dp[i][j] = Math.min(dp[i - 1][j - 1], Math.min(dp[i - 1][j], dp[i][j - 1])) + 1; + side = Math.max(side, dp[i][j]); + } + } + } + return side * side; +} +``` + ## 数组区间 ### 1. 数组区间和 @@ -336,6 +372,42 @@ public int numberOfArithmeticSlices(int[] A) { } ``` +### 3. 最长有效括号 + +32\. Longest Valid Parentheses (Hard) + +[Leetcode](https://leetcode.com/problems/longest-valid-parentheses/) / [力扣](https://leetcode.cn/problems/longest-valid-parentheses/) + +```text +Input: ")()())" +Output: 4 +``` + +dp[i] 表示以 s[i] 结尾的最长有效括号的长度,只有 s[i] 为 ) 时才可能不为 0。以 s[i - 1] 结尾的有效括号长度为 dp[i - 1],跳过这一段之后的位置 j = i - dp[i - 1] - 1 如果是 (,就能和 s[i] 配对,长度为 dp[i - 1] + 2,再加上 j 前面紧挨着的有效括号长度 dp[j - 1]。 + +```text +s : ( ) ( ( ) ) +dp : 0 2 0 0 2 6 +``` + +```java +public int longestValidParentheses(String s) { + int n = s.length(), ret = 0; + int[] dp = new int[n]; + for (int i = 1; i < n; i++) { + if (s.charAt(i) != ')') { + continue; + } + int j = i - dp[i - 1] - 1; + if (j >= 0 && s.charAt(j) == '(') { + dp[i] = dp[i - 1] + 2 + (j > 0 ? dp[j - 1] : 0); + ret = Math.max(ret, dp[i]); + } + } + return ret; +} +``` + ## 分割整数 ### 1. 分割整数的最大乘积 diff --git "a/notes/Leetcode \351\242\230\350\247\243 - \345\217\214\346\214\207\351\222\210.md" "b/notes/Leetcode \351\242\230\350\247\243 - \345\217\214\346\214\207\351\222\210.md" index 5d5113eb8..7b44d2154 100644 --- "a/notes/Leetcode \351\242\230\350\247\243 - \345\217\214\346\214\207\351\222\210.md" +++ "b/notes/Leetcode \351\242\230\350\247\243 - \345\217\214\346\214\207\351\222\210.md" @@ -8,6 +8,9 @@ * [5. 归并两个有序数组](#5-归并两个有序数组) * [6. 判断链表是否存在环](#6-判断链表是否存在环) * [7. 最长子序列](#7-最长子序列) + * [8. 三数之和](#8-三数之和) + * [9. 接雨水](#9-接雨水) + * [10. 最小覆盖子串](#10-最小覆盖子串) @@ -290,3 +293,124 @@ private boolean isSubstr(String s, String target) { return j == target.length(); } ``` + +## 8. 三数之和 + +15\. 3Sum (Medium) + +[Leetcode](https://leetcode.com/problems/3sum/) / [力扣](https://leetcode.cn/problems/3sum/) + +```text +Input: nums = [-1, 0, 1, 2, -1, -4] +Output: [[-1, -1, 2], [-1, 0, 1]] +``` + +题目描述:找出所有和为 0 且不重复的三元组。 + +先排序,然后枚举第一个数 nums[i],在它右边用第 1 题的双指针方法寻找和为 -nums[i] 的两个数。去重的关键是跳过相同的值:i 跳过与前一个数相同的值,找到一组解之后,两个指针也跳过相同的值。时间复杂度为 O(N2)。 + +```java +public List> threeSum(int[] nums) { + Arrays.sort(nums); + List> ret = new ArrayList<>(); + for (int i = 0; i < nums.length - 2; i++) { + if (i > 0 && nums[i] == nums[i - 1]) { + continue; + } + int l = i + 1, h = nums.length - 1; + while (l < h) { + int sum = nums[i] + nums[l] + nums[h]; + if (sum < 0) { + l++; + } else if (sum > 0) { + h--; + } else { + ret.add(Arrays.asList(nums[i], nums[l], nums[h])); + while (l < h && nums[l] == nums[l + 1]) { + l++; + } + while (l < h && nums[h] == nums[h - 1]) { + h--; + } + l++; + h--; + } + } + } + return ret; +} +``` + +## 9. 接雨水 + +42\. Trapping Rain Water (Hard) + +[Leetcode](https://leetcode.com/problems/trapping-rain-water/) / [力扣](https://leetcode.cn/problems/trapping-rain-water/) + +```text +Input: height = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1] +Output: 6 +``` + +位置 i 上方能存的水,取决于它左边最高的柱子和右边最高的柱子中较矮的那一个:water[i] = min(leftMax, rightMax) - height[i]。 + +用两个指针从两端向中间移动,分别维护 leftMax 和 rightMax。如果 leftMax < rightMax,左指针处的水位只由 leftMax 决定,因为右边已经有一根不低于 rightMax 的柱子,可以直接结算左指针并右移;反之结算右指针并左移。 + +

+ +```java +public int trap(int[] height) { + int l = 0, r = height.length - 1; + int leftMax = 0, rightMax = 0, ret = 0; + while (l < r) { + leftMax = Math.max(leftMax, height[l]); + rightMax = Math.max(rightMax, height[r]); + if (leftMax < rightMax) { + ret += leftMax - height[l++]; + } else { + ret += rightMax - height[r--]; + } + } + return ret; +} +``` + +## 10. 最小覆盖子串 + +76\. Minimum Window Substring (Hard) + +[Leetcode](https://leetcode.com/problems/minimum-window-substring/) / [力扣](https://leetcode.cn/problems/minimum-window-substring/) + +```text +Input: s = "ADOBECODEBANC", t = "ABC" +Output: "BANC" +``` + +题目描述:找出 s 中包含 t 所有字符(包括重复字符)的最短子串。 + +使用滑动窗口。need[c] 表示窗口还缺几个字符 c,missing 表示一共还缺几个字符。右指针不断扩大窗口,直到 missing 为 0,此时窗口包含了 t 的所有字符;然后移动左指针缩小窗口,在窗口仍然满足条件时更新答案。 + +```java +public String minWindow(String s, String t) { + int[] need = new int[128]; + for (char c : t.toCharArray()) { + need[c]++; + } + int missing = t.length(), start = 0, minLen = Integer.MAX_VALUE; + for (int l = 0, r = 0; r < s.length(); r++) { + if (need[s.charAt(r)]-- > 0) { + missing--; + } + while (missing == 0) { + if (r - l + 1 < minLen) { + minLen = r - l + 1; + start = l; + } + if (++need[s.charAt(l++)] > 0) { + missing++; + } + } + } + return minLen == Integer.MAX_VALUE ? "" : s.substring(start, start + minLen); +} +``` diff --git "a/notes/Leetcode \351\242\230\350\247\243 - \345\223\210\345\270\214\350\241\250.md" "b/notes/Leetcode \351\242\230\350\247\243 - \345\223\210\345\270\214\350\241\250.md" index 055a0fc6e..f535b8a26 100644 --- "a/notes/Leetcode \351\242\230\350\247\243 - \345\223\210\345\270\214\350\241\250.md" +++ "b/notes/Leetcode \351\242\230\350\247\243 - \345\223\210\345\270\214\350\241\250.md" @@ -5,6 +5,7 @@ * [2. 判断数组是否含有重复元素](#2-判断数组是否含有重复元素) * [3. 最长和谐序列](#3-最长和谐序列) * [4. 最长连续序列](#4-最长连续序列) + * [5. 和为 K 的子数组](#5-和为-k-的子数组) @@ -133,3 +134,32 @@ private int maxCount(Map countForNum) { return max; } ``` + +## 5. 和为 K 的子数组 + +560\. Subarray Sum Equals K (Medium) + +[Leetcode](https://leetcode.com/problems/subarray-sum-equals-k/) / [力扣](https://leetcode.cn/problems/subarray-sum-equals-k/) + +```text +Input: nums = [1, 1, 1], k = 2 +Output: 2 +``` + +题目描述:统计和为 k 的连续子数组个数,数组元素可能为负数。 + +用 sum 表示从数组开头到当前位置的前缀和,以当前位置结尾、和为 k 的子数组,对应前面某个位置的前缀和为 sum - k。遍历时用哈希表记录每个前缀和出现的次数即可。数组中有负数,窗口变大时和不一定变大,因此不能用滑动窗口。 + +```java +public int subarraySum(int[] nums, int k) { + Map cnt = new HashMap<>(); + cnt.put(0, 1); + int sum = 0, ret = 0; + for (int num : nums) { + sum += num; + ret += cnt.getOrDefault(sum - k, 0); + cnt.merge(sum, 1, Integer::sum); + } + return ret; +} +``` diff --git "a/notes/Leetcode \351\242\230\350\247\243 - \345\255\227\347\254\246\344\270\262.md" "b/notes/Leetcode \351\242\230\350\247\243 - \345\255\227\347\254\246\344\270\262.md" index 28d394828..fe94503e6 100644 --- "a/notes/Leetcode \351\242\230\350\247\243 - \345\255\227\347\254\246\344\270\262.md" +++ "b/notes/Leetcode \351\242\230\350\247\243 - \345\255\227\347\254\246\344\270\262.md" @@ -10,6 +10,9 @@ * [7. 回文子字符串个数](#7-回文子字符串个数) * [8. 判断一个整数是否是回文数](#8-判断一个整数是否是回文数) * [9. 统计二进制字符串中连续 1 和连续 0 数量相同的子字符串个数](#9-统计二进制字符串中连续-1-和连续-0-数量相同的子字符串个数) + * [10. 最长回文子串](#10-最长回文子串) + * [11. 字符串相乘](#11-字符串相乘) + * [12. 比较版本号](#12-比较版本号) @@ -237,3 +240,117 @@ public int countBinarySubstrings(String s) { return count; } ``` + +## 10. 最长回文子串 + +5\. Longest Palindromic Substring (Medium) + +[Leetcode](https://leetcode.com/problems/longest-palindromic-substring/) / [力扣](https://leetcode.cn/problems/longest-palindromic-substring/) + +```text +Input: s = "babad" +Output: "bab"("aba" 也是正确答案) +``` + +和第 7 题一样从中心扩展:回文串的中心可能是一个字符,也可能是两个字符之间,以每个位置为中心分别向两边扩展,记录最长的回文。时间复杂度为 O(N2),空间复杂度为 O(1)。 + +```java +private int start = 0, maxLen = 0; + +public String longestPalindrome(String s) { + for (int i = 0; i < s.length(); i++) { + extend(s, i, i); // 奇数长度 + extend(s, i, i + 1); // 偶数长度 + } + return s.substring(start, start + maxLen); +} + +private void extend(String s, int l, int r) { + while (l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) { + l--; + r++; + } + if (r - l - 1 > maxLen) { + maxLen = r - l - 1; + start = l + 1; + } +} +``` + +## 11. 字符串相乘 + +43\. Multiply Strings (Medium) + +[Leetcode](https://leetcode.com/problems/multiply-strings/) / [力扣](https://leetcode.cn/problems/multiply-strings/) + +```text +Input: num1 = "123", num2 = "45" +Output: "5535" +``` + +题目描述:以字符串形式给出两个非负整数,返回它们的乘积,不能把字符串直接转换为整数。 + +模拟竖式乘法。乘积最多有 m + n 位,用数组 digits 按从高位到低位的顺序保存。num1[i] 与 num2[j] 相乘的结果落在 digits[i + j + 1] 上,先把所有乘积累加到对应位置,最后从低位到高位统一处理进位。 + +```java +public String multiply(String num1, String num2) { + int m = num1.length(), n = num2.length(); + int[] digits = new int[m + n]; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + digits[i + j + 1] += (num1.charAt(i) - '0') * (num2.charAt(j) - '0'); + } + } + for (int k = m + n - 1; k > 0; k--) { + digits[k - 1] += digits[k] / 10; + digits[k] %= 10; + } + int k = 0; + while (k < m + n - 1 && digits[k] == 0) { + k++; + } + StringBuilder sb = new StringBuilder(); + for (; k < m + n; k++) { + sb.append(digits[k]); + } + return sb.toString(); +} +``` + +## 12. 比较版本号 + +165\. Compare Version Numbers (Medium) + +[Leetcode](https://leetcode.com/problems/compare-version-numbers/) / [力扣](https://leetcode.cn/problems/compare-version-numbers/) + +```text +Input: version1 = "1.01", version2 = "1.001" +Output: 0 +Input: version1 = "1.0.1", version2 = "1" +Output: 1 +``` + +题目描述:版本号由 . 分隔的修订号组成,从左到右依次比较修订号的整数值,前导 0 忽略,缺少的修订号视为 0。 + +用两个指针分别扫描两个字符串,每次各解析出一段修订号的整数值进行比较,不需要先把字符串分割成数组。 + +```java +public int compareVersion(String version1, String version2) { + int i = 0, j = 0; + while (i < version1.length() || j < version2.length()) { + int x = 0, y = 0; + while (i < version1.length() && version1.charAt(i) != '.') { + x = x * 10 + version1.charAt(i++) - '0'; + } + while (j < version2.length() && version2.charAt(j) != '.') { + y = y * 10 + version2.charAt(j++) - '0'; + } + if (x != y) { + return x > y ? 1 : -1; + } + i++; + j++; + } + return 0; +} +``` diff --git "a/notes/Leetcode \351\242\230\350\247\243 - \346\225\260\345\255\246.md" "b/notes/Leetcode \351\242\230\350\247\243 - \346\225\260\345\255\246.md" index 8060fbeaf..05da7be77 100644 --- "a/notes/Leetcode \351\242\230\350\247\243 - \346\225\260\345\255\246.md" +++ "b/notes/Leetcode \351\242\230\350\247\243 - \346\225\260\345\255\246.md" @@ -25,6 +25,7 @@ * [2. 3 的 n 次方](#2-3-的-n-次方) * [3. 乘积数组](#3-乘积数组) * [4. 找出数组中的乘积最大的三个数](#4-找出数组中的乘积最大的三个数) + * [5. 用 Rand7() 实现 Rand10()](#5-用-rand7-实现-rand10) @@ -533,3 +534,24 @@ public int maximumProduct(int[] nums) { return Math.max(max1*max2*max3, max1*min1*min2); } ``` + +### 5. 用 Rand7() 实现 Rand10() + +470\. Implement Rand10() Using Rand7() (Medium) + +[Leetcode](https://leetcode.com/problems/implement-rand10-using-rand7/) / [力扣](https://leetcode.cn/problems/implement-rand10-using-rand7/) + +题目描述:已知 rand7() 等概率返回 1 \~ 7,只用它实现等概率返回 1 \~ 10 的 rand10()。 + +把两次 rand7() 的结果看成一个两位的 7 进制数,(rand7() - 1) * 7 + rand7() 能等概率地生成 1 \~ 49。只保留 1 \~ 40,它们对 10 取余之后 0 \~ 9 各出现 4 次,仍然是等概率的;落在 41 \~ 49 时重新生成,这种方法称为拒绝采样。 + +```java +public int rand10() { + while (true) { + int x = (rand7() - 1) * 7 + rand7(); + if (x <= 40) { + return (x - 1) % 10 + 1; + } + } +} +``` diff --git "a/notes/Leetcode \351\242\230\350\247\243 - \346\225\260\347\273\204\344\270\216\347\237\251\351\230\265.md" "b/notes/Leetcode \351\242\230\350\247\243 - \346\225\260\347\273\204\344\270\216\347\237\251\351\230\265.md" index 7edc8eaf4..7e74755c1 100644 --- "a/notes/Leetcode \351\242\230\350\247\243 - \346\225\260\347\273\204\344\270\216\347\237\251\351\230\265.md" +++ "b/notes/Leetcode \351\242\230\350\247\243 - \346\225\260\347\273\204\344\270\216\347\237\251\351\230\265.md" @@ -13,6 +13,8 @@ * [10. 对角元素相等的矩阵](#10-对角元素相等的矩阵) * [11. 嵌套数组](#11-嵌套数组) * [12. 分隔数组](#12-分隔数组) + * [13. 合并区间](#13-合并区间) + * [14. 下一个排列](#14-下一个排列) @@ -452,3 +454,83 @@ public int maxChunksToSorted(int[] arr) { return ret; } ``` + +## 13. 合并区间 + +56\. Merge Intervals (Medium) + +[Leetcode](https://leetcode.com/problems/merge-intervals/) / [力扣](https://leetcode.cn/problems/merge-intervals/) + +```text +Input: [[1, 3], [2, 6], [8, 10], [15, 18]] +Output: [[1, 6], [8, 10], [15, 18]] +``` + +按区间的左端点排序后,能合并的区间一定是相邻的。依次检查每个区间:如果它的左端点不超过结果中最后一个区间的右端点,就把两者合并;否则作为一个新区间加入结果。 + +```java +public int[][] merge(int[][] intervals) { + Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0])); + List ret = new ArrayList<>(); + for (int[] cur : intervals) { + if (!ret.isEmpty() && cur[0] <= ret.get(ret.size() - 1)[1]) { + int[] last = ret.get(ret.size() - 1); + last[1] = Math.max(last[1], cur[1]); + } else { + ret.add(cur); + } + } + return ret.toArray(new int[ret.size()][]); +} +``` + +## 14. 下一个排列 + +31\. Next Permutation (Medium) + +[Leetcode](https://leetcode.com/problems/next-permutation/) / [力扣](https://leetcode.cn/problems/next-permutation/) + +```text +Input: [1, 2, 3] Output: [1, 3, 2] +Input: [3, 2, 1] Output: [1, 2, 3] +``` + +题目描述:把数组原地重排为字典序中下一个更大的排列,如果已经是最大的排列,就重排为最小的排列。 + +要让排列变大又变得尽量少,应该修改尽量靠右的位置: + +- 从右往左找到第一个满足 nums[i] < nums[i + 1] 的位置 i,i 右边是一段降序,已经是这一段能组成的最大排列; +- 从右往左找到第一个大于 nums[i] 的元素 nums[j],交换两者,i 位置变大了一点; +- 交换后 i 右边仍然是降序,把它反转为升序,使这一段尽量小。 + +```text +1 5 8 4 7 6 5 3 1 + i j +交换:1 5 8 5 7 6 4 3 1 +反转:1 5 8 5 1 3 4 6 7 +``` + +```java +public void nextPermutation(int[] nums) { + int i = nums.length - 2; + while (i >= 0 && nums[i] >= nums[i + 1]) { + i--; + } + if (i >= 0) { + int j = nums.length - 1; + while (nums[j] <= nums[i]) { + j--; + } + swap(nums, i, j); + } + for (int l = i + 1, r = nums.length - 1; l < r; l++, r--) { + swap(nums, l, r); + } +} + +private void swap(int[] nums, int i, int j) { + int t = nums[i]; + nums[i] = nums[j]; + nums[j] = t; +} +``` diff --git "a/notes/Leetcode \351\242\230\350\247\243 - \346\240\210\345\222\214\351\230\237\345\210\227.md" "b/notes/Leetcode \351\242\230\350\247\243 - \346\240\210\345\222\214\351\230\237\345\210\227.md" index e53e627d5..e7a1c092b 100644 --- "a/notes/Leetcode \351\242\230\350\247\243 - \346\240\210\345\222\214\351\230\237\345\210\227.md" +++ "b/notes/Leetcode \351\242\230\350\247\243 - \346\240\210\345\222\214\351\230\237\345\210\227.md" @@ -7,6 +7,8 @@ * [4. 用栈实现括号匹配](#4-用栈实现括号匹配) * [5. 数组中元素与下一个比它大的元素之间的距离](#5-数组中元素与下一个比它大的元素之间的距离) * [6. 循环数组中比当前元素大的下一个元素](#6-循环数组中比当前元素大的下一个元素) + * [7. 字符串解码](#7-字符串解码) + * [8. 基本计算器](#8-基本计算器) @@ -232,3 +234,91 @@ public int[] nextGreaterElements(int[] nums) { return next; } ``` + +## 7. 字符串解码 + +394\. Decode String (Medium) + +[Leetcode](https://leetcode.com/problems/decode-string/) / [力扣](https://leetcode.cn/problems/decode-string/) + +```text +Input: "3[a2[c]]" +Output: "accaccacc" +``` + +括号可以嵌套,用栈保存进入括号之前的状态:遇到 [ 时,把重复次数和当前已经得到的字符串分别压栈,然后从头开始记录括号内的字符串;遇到 ] 时弹出重复次数 k 和之前的字符串 prev,当前字符串变为 prev 加上括号内的字符串重复 k 次。 + +```java +public String decodeString(String s) { + Deque counts = new ArrayDeque<>(); + Deque prevs = new ArrayDeque<>(); + StringBuilder cur = new StringBuilder(); + int k = 0; + for (char c : s.toCharArray()) { + if (Character.isDigit(c)) { + k = k * 10 + c - '0'; + } else if (c == '[') { + counts.push(k); + prevs.push(cur); + cur = new StringBuilder(); + k = 0; + } else if (c == ']') { + String inner = cur.toString(); + cur = prevs.pop(); + for (int n = counts.pop(); n > 0; n--) { + cur.append(inner); + } + } else { + cur.append(c); + } + } + return cur.toString(); +} +``` + +## 8. 基本计算器 + +227\. Basic Calculator II (Medium) + +[Leetcode](https://leetcode.com/problems/basic-calculator-ii/) / [力扣](https://leetcode.cn/problems/basic-calculator-ii/) + +```text +Input: " 3+5 / 2 " +Output: 5 +``` + +题目描述:计算只包含非负整数、+、-、*、/ 和空格的表达式,整数除法向零截断。 + +乘除的优先级高于加减。读到一个数字之后,先不急着计算,等读到它后面的运算符时,再根据它前面的运算符处理:+ 把数字压栈,- 把相反数压栈,* 和 / 与栈顶元素计算后再放回。最后把栈中所有元素相加。 + +```java +public int calculate(String s) { + Deque stack = new ArrayDeque<>(); + char op = '+'; + int num = 0; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (Character.isDigit(c)) { + num = num * 10 + c - '0'; + } + if ((!Character.isDigit(c) && c != ' ') || i == s.length() - 1) { + if (op == '+') { + stack.push(num); + } else if (op == '-') { + stack.push(-num); + } else if (op == '*') { + stack.push(stack.pop() * num); + } else { + stack.push(stack.pop() / num); + } + op = c; + num = 0; + } + } + int ret = 0; + for (int x : stack) { + ret += x; + } + return ret; +} +``` diff --git "a/notes/Leetcode \351\242\230\350\247\243 - \351\223\276\350\241\250.md" "b/notes/Leetcode \351\242\230\350\247\243 - \351\223\276\350\241\250.md" index 270ec9ad7..188f91d62 100644 --- "a/notes/Leetcode \351\242\230\350\247\243 - \351\223\276\350\241\250.md" +++ "b/notes/Leetcode \351\242\230\350\247\243 - \351\223\276\350\241\250.md" @@ -11,6 +11,10 @@ * [8. 回文链表](#8-回文链表) * [9. 分隔链表](#9-分隔链表) * [10. 链表元素按奇偶聚集](#10-链表元素按奇偶聚集) + * [11. K 个一组翻转链表](#11-k-个一组翻转链表) + * [12. 重排链表](#12-重排链表) + * [13. 合并 K 个有序链表](#13-合并-k-个有序链表) + * [14. 链表排序](#14-链表排序) @@ -363,3 +367,170 @@ public ListNode oddEvenList(ListNode head) { return head; } ``` + +## 11. K 个一组翻转链表 + +25\. Reverse Nodes in k-Group (Hard) + +[Leetcode](https://leetcode.com/problems/reverse-nodes-in-k-group/) / [力扣](https://leetcode.cn/problems/reverse-nodes-in-k-group/) + +```text +Input: 1->2->3->4->5, k = 2 +Output: 2->1->4->3->5 +``` + +题目描述:每 k 个节点一组进行翻转,最后不足 k 个的节点保持原有顺序。 + +使用一个虚拟头节点 dummy,pre 指向下一组的前一个节点。先从 pre 向后走 k 步找到这一组的尾节点 tail,走不到说明剩余节点不足 k 个。然后把 pre.next 到 tail 这一段原地翻转,再把它接回原链表,最后 pre 移到这一组翻转后的末尾。 + +

+ +```java +public ListNode reverseKGroup(ListNode head, int k) { + ListNode dummy = new ListNode(-1); + dummy.next = head; + ListNode pre = dummy; + while (true) { + ListNode tail = pre; + for (int i = 0; i < k && tail != null; i++) { + tail = tail.next; + } + if (tail == null) { + break; + } + ListNode first = pre.next, next = tail.next; + ListNode prev = next, cur = first; + while (cur != next) { + ListNode tmp = cur.next; + cur.next = prev; + prev = cur; + cur = tmp; + } + pre.next = tail; + pre = first; + } + return dummy.next; +} +``` + +## 12. 重排链表 + +143\. Reorder List (Medium) + +[Leetcode](https://leetcode.com/problems/reorder-list/) / [力扣](https://leetcode.cn/problems/reorder-list/) + +```text +Input: 1->2->3->4->5 +Output: 1->5->2->4->3 +``` + +题目描述:把 L0→L1→…→Ln 重排为 L0→Ln→L1→Ln-1→…,只能修改节点的指针。 + +可以拆成三个已经解决过的子问题:用快慢指针找到中点并断开;把后半段反转;再把两段交替连接起来。 + +```java +public void reorderList(ListNode head) { + if (head == null || head.next == null) { + return; + } + ListNode slow = head, fast = head; + while (fast.next != null && fast.next.next != null) { + slow = slow.next; + fast = fast.next.next; + } + ListNode l2 = reverse(slow.next); + slow.next = null; + ListNode l1 = head; + while (l2 != null) { + ListNode n1 = l1.next, n2 = l2.next; + l1.next = l2; + l2.next = n1; + l1 = n1; + l2 = n2; + } +} + +private ListNode reverse(ListNode head) { + ListNode pre = null; + while (head != null) { + ListNode next = head.next; + head.next = pre; + pre = head; + head = next; + } + return pre; +} +``` + +## 13. 合并 K 个有序链表 + +23\. Merge k Sorted Lists (Hard) + +[Leetcode](https://leetcode.com/problems/merge-k-sorted-lists/) / [力扣](https://leetcode.cn/problems/merge-k-sorted-lists/) + +用小顶堆保存每个链表当前的头节点,每次取出值最小的节点接到结果链表后面,再把它的下一个节点放入堆中。设一共有 N 个节点,每个节点进出堆一次,时间复杂度为 O(NlogK)。 + +也可以像归并排序那样两两合并,时间复杂度同样是 O(NlogK)。 + +```java +public ListNode mergeKLists(ListNode[] lists) { + PriorityQueue pq = new PriorityQueue<>((a, b) -> Integer.compare(a.val, b.val)); + for (ListNode node : lists) { + if (node != null) { + pq.add(node); + } + } + ListNode dummy = new ListNode(-1), tail = dummy; + while (!pq.isEmpty()) { + ListNode node = pq.poll(); + tail.next = node; + tail = node; + if (node.next != null) { + pq.add(node.next); + } + } + return dummy.next; +} +``` + +## 14. 链表排序 + +148\. Sort List (Medium) + +[Leetcode](https://leetcode.com/problems/sort-list/) / [力扣](https://leetcode.cn/problems/sort-list/) + +题目描述:在 O(NlogN) 时间复杂度内对链表进行排序。 + +链表不能随机访问,适合用归并排序:用快慢指针找到中点断开,两半分别排序后,用第 3 题的方法归并。递归需要 O(logN) 的栈空间。 + +```java +public ListNode sortList(ListNode head) { + if (head == null || head.next == null) { + return head; + } + ListNode slow = head, fast = head.next; + while (fast != null && fast.next != null) { + slow = slow.next; + fast = fast.next.next; + } + ListNode right = slow.next; + slow.next = null; + return merge(sortList(head), sortList(right)); +} + +private ListNode merge(ListNode l1, ListNode l2) { + ListNode dummy = new ListNode(-1), tail = dummy; + while (l1 != null && l2 != null) { + if (l1.val <= l2.val) { + tail.next = l1; + l1 = l1.next; + } else { + tail.next = l2; + l2 = l2.next; + } + tail = tail.next; + } + tail.next = l1 != null ? l1 : l2; + return dummy.next; +} +``` diff --git a/notes/pics/reverse-k-group.png b/notes/pics/reverse-k-group.png new file mode 100644 index 000000000..42eef4770 Binary files /dev/null and b/notes/pics/reverse-k-group.png differ diff --git a/notes/pics/trapping-rain-water.png b/notes/pics/trapping-rain-water.png new file mode 100644 index 000000000..4aa666abd Binary files /dev/null and b/notes/pics/trapping-rain-water.png differ diff --git a/tools/diagram/src/reverse-k-group.svg b/tools/diagram/src/reverse-k-group.svg new file mode 100644 index 000000000..09f1d1f64 --- /dev/null +++ b/tools/diagram/src/reverse-k-group.svg @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + K 个一组翻转链表(k = 2) + 翻转前 + + + dummy + + + + 1 + + + + 2 + + + + 3 + + + + 4 + + + + 5 + + pre + + first + + tail + + next + 翻转后 + + + dummy + + + + 2 + + + + 1 + + + + 3 + + + + 4 + + + + 5 + + pre.next = tail + + pre = first + first 成为这一组的末尾,也是下一组的 pre + diff --git a/tools/diagram/src/trapping-rain-water.svg b/tools/diagram/src/trapping-rain-water.svg new file mode 100644 index 000000000..809b073a5 --- /dev/null +++ b/tools/diagram/src/trapping-rain-water.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + 接雨水 + 0 + + 1 + + 2 + + 3 + + + 4 + + 5 + + + 6 + + 7 + + 8 + + + 9 + + 10 + + 11 + + + leftMax = 2 + rightMax = 3 + + 位置 5 存水 min(2, 3) - 0 = 2 + 灰色为柱子,浅蓝色为积水,总共 6 +