Skip to content
40 changes: 40 additions & 0 deletions notes/Leetcode 题解 - 二分查找.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* [4. 第一个错误的版本](#4-第一个错误的版本)
* [5. 旋转数组的最小数字](#5-旋转数组的最小数字)
* [6. 查找区间](#6-查找区间)
* [7. 两个有序数组的中位数](#7-两个有序数组的中位数)
<!-- GFM-TOC -->


Expand Down Expand Up @@ -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 changes: 72 additions & 0 deletions notes/Leetcode 题解 - 动态规划.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
* [矩阵路径](#矩阵路径)
* [1. 矩阵的最小路径和](#1-矩阵的最小路径和)
* [2. 矩阵的总路径数](#2-矩阵的总路径数)
* [3. 最大正方形](#3-最大正方形)
* [数组区间](#数组区间)
* [1. 数组区间和](#1-数组区间和)
* [2. 数组中等差递增子区间的个数](#2-数组中等差递增子区间的个数)
* [3. 最长有效括号](#3-最长有效括号)
* [分割整数](#分割整数)
* [1. 分割整数的最大乘积](#1-分割整数的最大乘积)
* [2. 按平方数来分割整数](#2-按平方数来分割整数)
Expand Down Expand Up @@ -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. 数组区间和
Expand Down Expand Up @@ -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. 分割整数的最大乘积
Expand Down
124 changes: 124 additions & 0 deletions notes/Leetcode 题解 - 双指针.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
* [5. 归并两个有序数组](#5-归并两个有序数组)
* [6. 判断链表是否存在环](#6-判断链表是否存在环)
* [7. 最长子序列](#7-最长子序列)
* [8. 三数之和](#8-三数之和)
* [9. 接雨水](#9-接雨水)
* [10. 最小覆盖子串](#10-最小覆盖子串)
<!-- GFM-TOC -->


Expand Down Expand Up @@ -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 changes: 30 additions & 0 deletions notes/Leetcode 题解 - 哈希表.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* [2. 判断数组是否含有重复元素](#2-判断数组是否含有重复元素)
* [3. 最长和谐序列](#3-最长和谐序列)
* [4. 最长连续序列](#4-最长连续序列)
* [5. 和为 K 的子数组](#5-和为-k-的子数组)
<!-- GFM-TOC -->


Expand Down Expand Up @@ -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;
}
```
Loading
Loading