Skip to content

Commit 4572de9

Browse files
committed
auto commit
1 parent 96dfff3 commit 4572de9

File tree

1 file changed

+153
-81
lines changed

1 file changed

+153
-81
lines changed

notes/Leetcode 题解.md

Lines changed: 153 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
* [BST](#bst)
4545
* [Trie](#trie)
4646
* [](#图)
47+
* [二分图](#二分图)
4748
* [拓扑排序](#拓扑排序)
4849
* [并查集](#并查集)
4950
* [位运算](#位运算)
@@ -55,14 +56,19 @@
5556

5657
## 二分查找
5758

59+
**正常实现**
60+
5861
```java
59-
public int binarySearch(int key, int[] nums) {
62+
public int binarySearch(int[] nums, int key) {
6063
int l = 0, h = nums.length - 1;
6164
while (l <= h) {
62-
int mid = l + (h - l) / 2;
63-
if (key == nums[mid]) return mid;
64-
if (key < nums[mid]) h = mid - 1;
65-
else l = mid + 1;
65+
int m = l + (h - l) / 2;
66+
if (nums[m] == key)
67+
return m;
68+
else if (nums[m] > key)
69+
h = m - 1;
70+
else
71+
l = m + 1;
6672
}
6773
return -1;
6874
}
@@ -74,21 +80,59 @@ O(logN)
7480

7581
**计算 mid**
7682

77-
在计算 mid 时不能使用 mid = (l + h) / 2 这种方式,因为 l + h 可能会导致加法溢出,应该使用 mid = l + (h - l) / 2。
83+
有两种计算 mid 的方式:
7884

79-
**计算 h**
85+
- mid = (l + h) / 2
86+
- mid = l + (h - l) / 2
8087

81-
当循环条件为 l <= h,则 h = mid - 1。因为如果 h = mid,会出现循环无法退出的情况,例如 l = 1,h = 1,此时 mid 也等于 1,如果此时继续执行 h = mid,那么就会无限循环。
82-
83-
当循环条件为 l < h,则 h = mid。因为如果 h = mid - 1,会错误跳过查找的数,例如对于数组 [1,2,3],要查找 1,最开始 l = 0,h = 2,mid = 1,判断 key < arr[mid] 执行 h = mid - 1 = 0,此时循环退出,直接把查找的数跳过了。
88+
l + h 可能出现加法溢出,最好使用第二种方式。
8489

8590
**返回值**
8691

87-
在循环条件为 l <= h 的情况下,循环退出时 l 总是比 h 大 1,并且 l 是将 key 插入 nums 中的正确位置。例如对于 nums = {0,1,2,3},key = 4,循环退出时 l = 4,将 key 插入到 nums 中的第 4 个位置就能保持 nums 有序的特点。
92+
循环退出时如果仍然没有查找到 key,那么表示查找失败。可以有两种返回值:
93+
94+
- -1:以一个错误码指示没有查找到 key
95+
- l:将 key 插入到 nums 中的正确位置
96+
97+
**变种**
98+
99+
二分查找可以有很多变种,变种实现要多注意边界值的判断。例如在一个有重复元素的数组中查找 key 的最左位置的实现如下:
100+
101+
```java
102+
public int binarySearch(int[] nums, int key) {
103+
int l = 0, h = nums.length - 1;
104+
while (l < h) {
105+
int m = l + (h - l) / 2;
106+
if (nums[m] >= key)
107+
h = m;
108+
else
109+
l = m + 1;
110+
}
111+
return l;
112+
}
113+
```
114+
115+
该实现和正常实现有以下不同:
116+
117+
- 循环条件为 l < h
118+
- h 的赋值表达式为 h = m
119+
- 最后返回 l 而不是 -1
88120

89-
在循环条件为 l < h 的情况下,循环退出时 l 和 h 相等
121+
在 nums[m] >= key 的情况下,可以推导出最左 key 位于 [0, m] 区间中,这是一个闭区间。h 的赋值表达式为 h = m,因为 m 位置也可能是解
90122

91-
如果只是想知道 key 存不存在,在循环退出之后可以直接返回 -1 表示 key 不存在于 nums 中。
123+
在 h 的赋值表达式为 h = mid 的情况下,如果循环条件为 l <= h,那么会出现循环无法退出的情况,因此循环条件只能是 l < h。
124+
125+
```text
126+
nums = {0, 1}, key = 0
127+
l m h
128+
0 1 2 nums[m] >= key
129+
0 0 1 nums[m] >= key
130+
0 0 0 nums[m] >= key
131+
0 0 0 nums[m] >= key
132+
...
133+
```
134+
135+
当循环体退出时,不表示没有查找到 key,因此最后返回的结果不应该为 -1。为了验证有没有查找到,需要在调用端判断一下返回位置上的值和 key 是否相等。
92136

93137
**求开方**
94138

@@ -109,66 +153,23 @@ Explanation: The square root of 8 is 2.82842..., and since we want to return an
109153

110154
```java
111155
public int mySqrt(int x) {
112-
if (x <= 1) return x;
156+
if (x <= 1)
157+
return x;
113158
int l = 1, h = x;
114159
while (l <= h) {
115160
int mid = l + (h - l) / 2;
116161
int sqrt = x / mid;
117-
if (sqrt == mid) return mid;
118-
if (sqrt < mid) h = mid - 1;
119-
else l = mid + 1;
162+
if (sqrt == mid)
163+
return mid;
164+
else if (sqrt < mid)
165+
h = mid - 1;
166+
else
167+
l = mid + 1;
120168
}
121169
return h;
122170
}
123171
```
124172

125-
**摆硬币**
126-
127-
[Leetcode : 441. Arranging Coins (Easy)](https://leetcode.com/problems/arranging-coins/description/)
128-
129-
```html
130-
n = 8
131-
The coins can form the following rows:
132-
¤
133-
¤ ¤
134-
¤ ¤ ¤
135-
¤ ¤
136-
Because the 4th row is incomplete, we return 3.
137-
```
138-
139-
题目描述:第 i 行摆 i 个,统计能够摆的行数。
140-
141-
n 个硬币能够摆的行数 row 在 0 \~ n 之间,并且满足 n == row * (row + 1) / 2,因此可以利用二分查找在 0 \~ n 之间查找 row。
142-
143-
对于 n = 8,它能摆的行数 row = 3,这是因为最后没有摆满的那一行不能算进去,因此在循环退出时应该返回 h。
144-
145-
```java
146-
public int arrangeCoins(int n) {
147-
int l = 0, h = n;
148-
while (l <= h) {
149-
int mid = l + (h - l) / 2;
150-
long x = mid * (mid + 1) / 2;
151-
if (x == n) return mid;
152-
else if (x < n) l = mid + 1;
153-
else h = mid - 1;
154-
}
155-
return h;
156-
}
157-
```
158-
159-
本题可以不用二分查找,更直观的解法如下:
160-
161-
```java
162-
public int arrangeCoins(int n) {
163-
int level = 1;
164-
while (n > 0) {
165-
n -= level;
166-
level++;
167-
}
168-
return n == 0 ? level - 1 : level - 2;
169-
}
170-
```
171-
172173
**大于给定元素的最小元素**
173174

174175
[Leetcode : 744. Find Smallest Letter Greater Than Target (Easy)](https://leetcode.com/problems/find-smallest-letter-greater-than-target/description/)
@@ -195,8 +196,10 @@ public char nextGreatestLetter(char[] letters, char target) {
195196
int l = 0, h = n - 1;
196197
while (l <= h) {
197198
int m = l + (h - l) / 2;
198-
if (letters[m] <= target) l = m + 1;
199-
else h = m - 1;
199+
if (letters[m] <= target)
200+
l = m + 1;
201+
else
202+
h = m - 1;
200203
}
201204
return l < n ? letters[l] : letters[0];
202205
}
@@ -213,9 +216,9 @@ Output: 2
213216

214217
题目描述:一个有序数组只有一个数不出现两次,找出这个数。要求以 O(logN) 时间复杂度进行求解。
215218

216-
令 key 为 Single Element 在数组中的位置。如果 m 为偶数,并且 m < key,那么 nums[m] == nums[m + 1];m >= key,那么 nums[m] != nums[m + 1]
219+
令 key 为 Single Element 在数组中的位置。如果 m 为偶数,并且 m + 1 < key,那么 nums[m] == nums[m + 1];m + 1 >= key,那么 nums[m] != nums[m + 1]
217220

218-
从上面的规律可以知道,如果 nums[m] == nums[m + 1],那么 key 所在的数组位置为 m + 2 \~ n - 1,此时令 l = m + 2;如果 nums[m] != nums[m + 1],那么 key 所在的数组位置为 0 \~ m,此时令 h = m。
221+
从上面的规律可以知道,如果 nums[m] == nums[m + 1],那么 key 所在的数组位置为 [m + 2, n - 1],此时令 l = m + 2;如果 nums[m] != nums[m + 1],那么 key 所在的数组位置为 [0, m],此时令 h = m。
219222

220223
因为 h 的赋值表达式为 h = m,那么循环条件也就只能使用 l < h 这种形式。
221224

@@ -224,9 +227,12 @@ public int singleNonDuplicate(int[] nums) {
224227
int l = 0, h = nums.length - 1;
225228
while (l < h) {
226229
int m = l + (h - l) / 2;
227-
if (m % 2 == 1) m--; // 保证 l/h/m 都在偶数位,使得查找区间大小一直都是奇数
228-
if (nums[m] == nums[m + 1]) l = m + 2;
229-
else h = m;
230+
if (m % 2 == 1)
231+
m--; // 保证 l/h/m 都在偶数位,使得查找区间大小一直都是奇数
232+
if (nums[m] == nums[m + 1])
233+
l = m + 2;
234+
else
235+
h = m;
230236
}
231237
return nums[l];
232238
}
@@ -238,17 +244,19 @@ public int singleNonDuplicate(int[] nums) {
238244

239245
题目描述:给定一个元素 n 代表有 [1, 2, ..., n] 版本,可以调用 isBadVersion(int x) 知道某个版本是否错误,要求找到第一个错误的版本。
240246

241-
如果第 m 个版本出错,则表示第一个错误的版本在 1 \~ m 之前,令 h = m;否则第一个错误的版本在 m + 1 \~ n 之间,令 l = m + 1。
247+
如果第 m 个版本出错,则表示第一个错误的版本在 [1, m] 之间,令 h = m;否则第一个错误的版本在 [m + 1, n] 之间,令 l = m + 1。
242248

243249
因为 h 的赋值表达式为 h = m,因此循环条件为 l < h。
244250

245251
```java
246252
public int firstBadVersion(int n) {
247253
int l = 1, h = n;
248254
while (l < h) {
249-
int m = l + (h - l) / 2;
250-
if (isBadVersion(m)) h = m;
251-
else l = m + 1;
255+
int mid = l + (h - l) / 2;
256+
if (isBadVersion(mid))
257+
h = mid;
258+
else
259+
l = mid + 1;
252260
}
253261
return l;
254262
}
@@ -268,8 +276,10 @@ public int findMin(int[] nums) {
268276
int l = 0, h = nums.length - 1;
269277
while (l < h) {
270278
int m = l + (h - l) / 2;
271-
if (nums[m] <= nums[h]) h = m;
272-
else l = m + 1;
279+
if (nums[m] <= nums[h])
280+
h = m;
281+
else
282+
l = m + 1;
273283
}
274284
return nums[l];
275285
}
@@ -291,16 +301,20 @@ Output: [-1,-1]
291301
public int[] searchRange(int[] nums, int target) {
292302
int first = binarySearch(nums, target);
293303
int last = binarySearch(nums, target + 1) - 1;
294-
if (first == nums.length || nums[first] != target) return new int[]{-1, -1};
295-
return new int[]{first, Math.max(first, last)};
304+
if (first == nums.length || nums[first] != target)
305+
return new int[]{-1, -1};
306+
else
307+
return new int[]{first, Math.max(first, last)};
296308
}
297309

298310
private int binarySearch(int[] nums, int target) {
299311
int l = 0, h = nums.length; // 注意 h 的初始值
300312
while (l < h) {
301313
int m = l + (h - l) / 2;
302-
if (nums[m] >= target) h = m;
303-
else l = m + 1;
314+
if (nums[m] >= target)
315+
h = m;
316+
else
317+
l = m + 1;
304318
}
305319
return l;
306320
}
@@ -5914,6 +5928,64 @@ class MapSum {
59145928

59155929
##
59165930

5931+
### 二分图
5932+
5933+
如果可以用两种颜色对图中的节点进行着色,并且保证相邻的节点颜色不同,那么这个图就是二分图。
5934+
5935+
**判断是否为二分图**
5936+
5937+
[Leetcode : 785. Is Graph Bipartite? (Medium)](https://leetcode.com/problems/is-graph-bipartite/description/)
5938+
5939+
```html
5940+
Input: [[1,3], [0,2], [1,3], [0,2]]
5941+
Output: true
5942+
Explanation:
5943+
The graph looks like this:
5944+
0----1
5945+
| |
5946+
| |
5947+
3----2
5948+
We can divide the vertices into two groups: {0, 2} and {1, 3}.
5949+
```
5950+
5951+
```html
5952+
Example 2:
5953+
Input: [[1,2,3], [0,2], [0,1,3], [0,2]]
5954+
Output: false
5955+
Explanation:
5956+
The graph looks like this:
5957+
0----1
5958+
| \ |
5959+
| \ |
5960+
3----2
5961+
We cannot find a way to divide the set of nodes into two independent subsets.
5962+
```
5963+
5964+
```java
5965+
public boolean isBipartite(int[][] graph) {
5966+
int[] colors = new int[graph.length];
5967+
Arrays.fill(colors, -1);
5968+
5969+
for (int i = 0; i < graph.length; i++)
5970+
if (colors[i] != -1 && !isBipartite(graph, i, 0, colors))
5971+
return false;
5972+
5973+
return true;
5974+
}
5975+
5976+
private boolean isBipartite(int[][] graph, int cur, int color, int[] colors) {
5977+
if (colors[cur] != -1)
5978+
return colors[cur] == color;
5979+
5980+
colors[cur] = color;
5981+
for (int next : graph[cur])
5982+
if (!isBipartite(graph, next, 1 - color, colors))
5983+
return false;
5984+
5985+
return true;
5986+
}
5987+
```
5988+
59175989
### 拓扑排序
59185990

59195991
常用于在具有先序关系的任务规划中。

0 commit comments

Comments
 (0)