Merge pull request #1458 from CyC2018/add-codetop-problems

补充常见面试算法题
This commit is contained in:
CyC2018
2026-10-25 22:48:13 +08:00
committed by GitHub
13 changed files with 910 additions and 0 deletions
+40
View File
@@ -7,6 +7,7 @@
* [4. 第一个错误的版本](#4-第一个错误的版本)
* [5. 旋转数组的最小数字](#5-旋转数组的最小数字)
* [6. 查找区间](#6-查找区间)
* [7. 两个有序数组的中位数](#7-两个有序数组的中位数)
<!-- GFM-TOC -->
@@ -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));
}
```
+72
View File
@@ -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. 分割整数的最大乘积
+124
View File
@@ -8,6 +8,9 @@
* [5. 归并两个有序数组](#5-归并两个有序数组)
* [6. 判断链表是否存在环](#6-判断链表是否存在环)
* [7. 最长子序列](#7-最长子序列)
* [8. 三数之和](#8-三数之和)
* [9. 接雨水](#9-接雨水)
* [10. 最小覆盖子串](#10-最小覆盖子串)
<!-- GFM-TOC -->
@@ -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(N<sup>2</sup>)。
```java
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> 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 的柱子,可以直接结算左指针并右移;反之结算右指针并左移。
<div align="center"> <img src="pics/trapping-rain-water.png" width="560px"/> </div><br>
```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);
}
```
+30
View File
@@ -5,6 +5,7 @@
* [2. 判断数组是否含有重复元素](#2-判断数组是否含有重复元素)
* [3. 最长和谐序列](#3-最长和谐序列)
* [4. 最长连续序列](#4-最长连续序列)
* [5. 和为 K 的子数组](#5-和为-k-的子数组)
<!-- GFM-TOC -->
@@ -133,3 +134,32 @@ private int maxCount(Map<Integer, Integer> 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<Integer, Integer> 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;
}
```
+117
View File
@@ -10,6 +10,9 @@
* [7. 回文子字符串个数](#7-回文子字符串个数)
* [8. 判断一个整数是否是回文数](#8-判断一个整数是否是回文数)
* [9. 统计二进制字符串中连续 1 和连续 0 数量相同的子字符串个数](#9-统计二进制字符串中连续-1-和连续-0-数量相同的子字符串个数)
* [10. 最长回文子串](#10-最长回文子串)
* [11. 字符串相乘](#11-字符串相乘)
* [12. 比较版本号](#12-比较版本号)
<!-- GFM-TOC -->
@@ -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(N<sup>2</sup>),空间复杂度为 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;
}
```
+22
View File
@@ -25,6 +25,7 @@
* [2. 3 的 n 次方](#2-3-的-n-次方)
* [3. 乘积数组](#3-乘积数组)
* [4. 找出数组中的乘积最大的三个数](#4-找出数组中的乘积最大的三个数)
* [5. 用 Rand7() 实现 Rand10()](#5-用-rand7-实现-rand10)
<!-- GFM-TOC -->
@@ -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;
}
}
}
```
@@ -13,6 +13,8 @@
* [10. 对角元素相等的矩阵](#10-对角元素相等的矩阵)
* [11. 嵌套数组](#11-嵌套数组)
* [12. 分隔数组](#12-分隔数组)
* [13. 合并区间](#13-合并区间)
* [14. 下一个排列](#14-下一个排列)
<!-- GFM-TOC -->
@@ -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<int[]> 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;
}
```
+90
View File
@@ -7,6 +7,8 @@
* [4. 用栈实现括号匹配](#4-用栈实现括号匹配)
* [5. 数组中元素与下一个比它大的元素之间的距离](#5-数组中元素与下一个比它大的元素之间的距离)
* [6. 循环数组中比当前元素大的下一个元素](#6-循环数组中比当前元素大的下一个元素)
* [7. 字符串解码](#7-字符串解码)
* [8. 基本计算器](#8-基本计算器)
<!-- GFM-TOC -->
@@ -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<Integer> counts = new ArrayDeque<>();
Deque<StringBuilder> 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<Integer> 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;
}
```
+171
View File
@@ -11,6 +11,10 @@
* [8. 回文链表](#8-回文链表)
* [9. 分隔链表](#9-分隔链表)
* [10. 链表元素按奇偶聚集](#10-链表元素按奇偶聚集)
* [11. K 个一组翻转链表](#11-k-个一组翻转链表)
* [12. 重排链表](#12-重排链表)
* [13. 合并 K 个有序链表](#13-合并-k-个有序链表)
* [14. 链表排序](#14-链表排序)
<!-- GFM-TOC -->
@@ -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 移到这一组翻转后的末尾。
<div align="center"> <img src="pics/reverse-k-group.png" width="560px"/> </div><br>
```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<ListNode> 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;
}
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

+95
View File
@@ -0,0 +1,95 @@
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="300" viewBox="0 0 600 300">
<style>
text { font-family: "PingFang SC", "Microsoft YaHei", Helvetica, Arial, sans-serif; fill: #666; font-size: 12px; }
.title { font-weight: bold; font-size: 15px; fill: #777; text-anchor: middle; }
.b { font-weight: bold; }
.c { text-anchor: middle; }
.e { text-anchor: end; }
.note { fill: #C0605A; }
.ok { fill: #6A9F68; }
.sub { font-size: 11px; fill: #888; }
.mono { font-family: Menlo, Consolas, monospace; }
.arrow { stroke: #666; stroke-width: 1.3; fill: none; marker-end: url(#a); }
.red { stroke: #C0605A; marker-end: url(#r); }
.green { stroke: #6A9F68; marker-end: url(#g); }
.dash { stroke-dasharray: 5 4; }
.line { stroke: #666; stroke-width: 1.3; fill: none; }
.frame { fill: none; stroke: #999; stroke-width: 1; stroke-dasharray: 4 3; }
.shadow { fill: #DADADA; }
</style>
<defs>
<marker id="a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1 L9,5 L1,9" fill="none" stroke="#666" stroke-width="1.6"/>
</marker>
<marker id="r" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1 L9,5 L1,9" fill="none" stroke="#C0605A" stroke-width="1.6"/>
</marker>
<marker id="g" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1 L9,5 L1,9" fill="none" stroke="#6A9F68" stroke-width="1.6"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#FFFFFF"/>
<text x="300" y="28" class="title">K 个一组翻转链表(k = 2)</text>
<text x="20" y="90" class="b">翻转前</text>
<rect x="87" y="71" width="50" height="32" rx="6" class="shadow"/>
<rect x="85" y="69" width="50" height="32" rx="6" fill="#E6E6E6"/>
<text x="110" y="89.5" class="c b">dummy</text>
<line x1="137" y1="85" x2="163" y2="85" class="arrow "/>
<rect x="167" y="71" width="50" height="32" rx="6" class="shadow"/>
<rect x="165" y="69" width="50" height="32" rx="6" fill="#B1DDF0"/>
<text x="190" y="89.5" class="c b">1</text>
<line x1="217" y1="85" x2="243" y2="85" class="arrow "/>
<rect x="247" y="71" width="50" height="32" rx="6" class="shadow"/>
<rect x="245" y="69" width="50" height="32" rx="6" fill="#B1DDF0"/>
<text x="270" y="89.5" class="c b">2</text>
<line x1="297" y1="85" x2="323" y2="85" class="arrow "/>
<rect x="327" y="71" width="50" height="32" rx="6" class="shadow"/>
<rect x="325" y="69" width="50" height="32" rx="6" fill="#B1DDF0"/>
<text x="350" y="89.5" class="c b">3</text>
<line x1="377" y1="85" x2="403" y2="85" class="arrow "/>
<rect x="407" y="71" width="50" height="32" rx="6" class="shadow"/>
<rect x="405" y="69" width="50" height="32" rx="6" fill="#B1DDF0"/>
<text x="430" y="89.5" class="c b">4</text>
<line x1="457" y1="85" x2="483" y2="85" class="arrow "/>
<rect x="487" y="71" width="50" height="32" rx="6" class="shadow"/>
<rect x="485" y="69" width="50" height="32" rx="6" fill="#B1DDF0"/>
<text x="510" y="89.5" class="c b">5</text>
<line x1="110" y1="131" x2="110" y2="105" class="arrow "/>
<text x="110" y="147" class="c ">pre</text>
<line x1="190" y1="131" x2="190" y2="105" class="arrow "/>
<text x="190" y="147" class="c ">first</text>
<line x1="270" y1="131" x2="270" y2="105" class="arrow "/>
<text x="270" y="147" class="c ">tail</text>
<line x1="350" y1="131" x2="350" y2="105" class="arrow "/>
<text x="350" y="147" class="c ">next</text>
<text x="20" y="205" class="b">翻转后</text>
<rect x="87" y="186" width="50" height="32" rx="6" class="shadow"/>
<rect x="85" y="184" width="50" height="32" rx="6" fill="#E6E6E6"/>
<text x="110" y="204.5" class="c b">dummy</text>
<line x1="137" y1="200" x2="163" y2="200" class="arrow "/>
<rect x="167" y="186" width="50" height="32" rx="6" class="shadow"/>
<rect x="165" y="184" width="50" height="32" rx="6" fill="#B1DDF0"/>
<text x="190" y="204.5" class="c b">2</text>
<line x1="217" y1="200" x2="243" y2="200" class="arrow "/>
<rect x="247" y="186" width="50" height="32" rx="6" class="shadow"/>
<rect x="245" y="184" width="50" height="32" rx="6" fill="#B1DDF0"/>
<text x="270" y="204.5" class="c b">1</text>
<line x1="297" y1="200" x2="323" y2="200" class="arrow "/>
<rect x="327" y="186" width="50" height="32" rx="6" class="shadow"/>
<rect x="325" y="184" width="50" height="32" rx="6" fill="#B1DDF0"/>
<text x="350" y="204.5" class="c b">3</text>
<line x1="377" y1="200" x2="403" y2="200" class="arrow "/>
<rect x="407" y="186" width="50" height="32" rx="6" class="shadow"/>
<rect x="405" y="184" width="50" height="32" rx="6" fill="#B1DDF0"/>
<text x="430" y="204.5" class="c b">4</text>
<line x1="457" y1="200" x2="483" y2="200" class="arrow "/>
<rect x="487" y="186" width="50" height="32" rx="6" class="shadow"/>
<rect x="485" y="184" width="50" height="32" rx="6" fill="#B1DDF0"/>
<text x="510" y="204.5" class="c b">5</text>
<line x1="110" y1="246" x2="110" y2="220" class="arrow "/>
<text x="110" y="262" class="c ">pre.next = tail</text>
<line x1="270" y1="246" x2="270" y2="220" class="arrow green"/>
<text x="270" y="262" class="c ok">pre = first</text>
<text x="300" y="292" class="c sub">first 成为这一组的末尾,也是下一组的 pre</text>
</svg>

After

Width:  |  Height:  |  Size: 5.3 KiB

+67
View File
@@ -0,0 +1,67 @@
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="290" viewBox="0 0 600 290">
<style>
text { font-family: "PingFang SC", "Microsoft YaHei", Helvetica, Arial, sans-serif; fill: #666; font-size: 12px; }
.title { font-weight: bold; font-size: 15px; fill: #777; text-anchor: middle; }
.b { font-weight: bold; }
.c { text-anchor: middle; }
.e { text-anchor: end; }
.note { fill: #C0605A; }
.ok { fill: #6A9F68; }
.sub { font-size: 11px; fill: #888; }
.mono { font-family: Menlo, Consolas, monospace; }
.arrow { stroke: #666; stroke-width: 1.3; fill: none; marker-end: url(#a); }
.red { stroke: #C0605A; marker-end: url(#r); }
.green { stroke: #6A9F68; marker-end: url(#g); }
.dash { stroke-dasharray: 5 4; }
.line { stroke: #666; stroke-width: 1.3; fill: none; }
.frame { fill: none; stroke: #999; stroke-width: 1; stroke-dasharray: 4 3; }
.shadow { fill: #DADADA; }
</style>
<defs>
<marker id="a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1 L9,5 L1,9" fill="none" stroke="#666" stroke-width="1.6"/>
</marker>
<marker id="r" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1 L9,5 L1,9" fill="none" stroke="#C0605A" stroke-width="1.6"/>
</marker>
<marker id="g" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1 L9,5 L1,9" fill="none" stroke="#6A9F68" stroke-width="1.6"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#FFFFFF"/>
<text x="300" y="28" class="title">接雨水</text>
<text x="78" y="246" class="c sub">0</text>
<rect x="98" y="190" width="36" height="40" fill="#9E9E9E"/>
<text x="116" y="246" class="c sub">1</text>
<rect x="136" y="190" width="36" height="40" fill="#D6EEF8"/>
<text x="154" y="246" class="c sub">2</text>
<rect x="174" y="150" width="36" height="80" fill="#9E9E9E"/>
<text x="192" y="246" class="c sub">3</text>
<rect x="212" y="150" width="36" height="40" fill="#D6EEF8"/>
<rect x="212" y="190" width="36" height="40" fill="#9E9E9E"/>
<text x="230" y="246" class="c sub">4</text>
<rect x="250" y="150" width="36" height="80" fill="#D6EEF8"/>
<text x="268" y="246" class="c sub">5</text>
<rect x="288" y="150" width="36" height="40" fill="#D6EEF8"/>
<rect x="288" y="190" width="36" height="40" fill="#9E9E9E"/>
<text x="306" y="246" class="c sub">6</text>
<rect x="326" y="110" width="36" height="120" fill="#9E9E9E"/>
<text x="344" y="246" class="c sub">7</text>
<rect x="364" y="150" width="36" height="80" fill="#9E9E9E"/>
<text x="382" y="246" class="c sub">8</text>
<rect x="402" y="150" width="36" height="40" fill="#D6EEF8"/>
<rect x="402" y="190" width="36" height="40" fill="#9E9E9E"/>
<text x="420" y="246" class="c sub">9</text>
<rect x="440" y="150" width="36" height="80" fill="#9E9E9E"/>
<text x="458" y="246" class="c sub">10</text>
<rect x="478" y="190" width="36" height="40" fill="#9E9E9E"/>
<text x="496" y="246" class="c sub">11</text>
<line x1="54" y1="230" x2="516" y2="230" class="line"/>
<line x1="174" y1="150" x2="364" y2="150" class="line dash"/>
<text x="168" y="144" class="e note">leftMax = 2</text>
<text x="368" y="124" class="note">rightMax = 3</text>
<line x1="268" y1="86" x2="268" y2="146" class="arrow red"/>
<text x="208" y="80" class="c">位置 5 存水 min(2, 3) - 0 = 2</text>
<text x="300" y="278" class="c sub">灰色为柱子,浅蓝色为积水,总共 6</text>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB