diff --git a/notes/Leetcode 题解 - 双指针.md b/notes/Leetcode 题解 - 双指针.md index 5d5113eb..7b44d215 100644 --- a/notes/Leetcode 题解 - 双指针.md +++ b/notes/Leetcode 题解 - 双指针.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/pics/trapping-rain-water.png b/notes/pics/trapping-rain-water.png new file mode 100644 index 00000000..4aa666ab Binary files /dev/null and b/notes/pics/trapping-rain-water.png differ diff --git a/tools/diagram/src/trapping-rain-water.svg b/tools/diagram/src/trapping-rain-water.svg new file mode 100644 index 00000000..6c6614d9 --- /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 +