44
44
* [ BST] ( #bst )
45
45
* [ Trie] ( #trie )
46
46
* [ 图] ( #图 )
47
+ * [ 二分图] ( #二分图 )
47
48
* [ 拓扑排序] ( #拓扑排序 )
48
49
* [ 并查集] ( #并查集 )
49
50
* [ 位运算] ( #位运算 )
55
56
56
57
## 二分查找
57
58
59
+ ** 正常实现**
60
+
58
61
``` java
59
- public int binarySearch(int key , int [] nums ) {
62
+ public int binarySearch(int [] nums , int key ) {
60
63
int l = 0 , h = nums. length - 1 ;
61
64
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 ;
66
72
}
67
73
return - 1 ;
68
74
}
@@ -74,21 +80,59 @@ O(logN)
74
80
75
81
** 计算 mid**
76
82
77
- 在计算 mid 时不能使用 mid = (l + h) / 2 这种方式,因为 l + h 可能会导致加法溢出,应该使用 mid = l + (h - l) / 2。
83
+ 有两种计算 mid 的方式:
78
84
79
- ** 计算 h**
85
+ - mid = (l + h) / 2
86
+ - mid = l + (h - l) / 2
80
87
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 可能出现加法溢出,最好使用第二种方式。
84
89
85
90
** 返回值**
86
91
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
88
120
89
- 在循环条件为 l < h 的情况下,循环退出时 l 和 h 相等 。
121
+ 在 nums [ m ] >= key 的情况下,可以推导出最左 key 位于 [ 0, m ] 区间中,这是一个闭区间。h 的赋值表达式为 h = m,因为 m 位置也可能是解 。
90
122
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 是否相等。
92
136
93
137
** 求开方**
94
138
@@ -109,66 +153,23 @@ Explanation: The square root of 8 is 2.82842..., and since we want to return an
109
153
110
154
``` java
111
155
public int mySqrt(int x) {
112
- if (x <= 1 ) return x;
156
+ if (x <= 1 )
157
+ return x;
113
158
int l = 1 , h = x;
114
159
while (l <= h) {
115
160
int mid = l + (h - l) / 2 ;
116
161
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 ;
120
168
}
121
169
return h;
122
170
}
123
171
```
124
172
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
-
172
173
** 大于给定元素的最小元素**
173
174
174
175
[ 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) {
195
196
int l = 0 , h = n - 1 ;
196
197
while (l <= h) {
197
198
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 ;
200
203
}
201
204
return l < n ? letters[l] : letters[0 ];
202
205
}
@@ -213,9 +216,9 @@ Output: 2
213
216
214
217
题目描述:一个有序数组只有一个数不出现两次,找出这个数。要求以 O(logN) 时间复杂度进行求解。
215
218
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] 。
217
220
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。
219
222
220
223
因为 h 的赋值表达式为 h = m,那么循环条件也就只能使用 l < h 这种形式。
221
224
@@ -224,9 +227,12 @@ public int singleNonDuplicate(int[] nums) {
224
227
int l = 0 , h = nums. length - 1 ;
225
228
while (l < h) {
226
229
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;
230
236
}
231
237
return nums[l];
232
238
}
@@ -238,17 +244,19 @@ public int singleNonDuplicate(int[] nums) {
238
244
239
245
题目描述:给定一个元素 n 代表有 [ 1, 2, ..., n] 版本,可以调用 isBadVersion(int x) 知道某个版本是否错误,要求找到第一个错误的版本。
240
246
241
- 如果第 m 个版本出错,则表示第一个错误的版本在 1 \~ m 之前 ,令 h = m;否则第一个错误的版本在 m + 1 \~ n 之间,令 l = m + 1。
247
+ 如果第 m 个版本出错,则表示第一个错误的版本在 [ 1, m ] 之间 ,令 h = m;否则第一个错误的版本在 [ m + 1, n ] 之间,令 l = m + 1。
242
248
243
249
因为 h 的赋值表达式为 h = m,因此循环条件为 l < h。
244
250
245
251
``` java
246
252
public int firstBadVersion(int n) {
247
253
int l = 1 , h = n;
248
254
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 ;
252
260
}
253
261
return l;
254
262
}
@@ -268,8 +276,10 @@ public int findMin(int[] nums) {
268
276
int l = 0 , h = nums. length - 1 ;
269
277
while (l < h) {
270
278
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 ;
273
283
}
274
284
return nums[l];
275
285
}
@@ -291,16 +301,20 @@ Output: [-1,-1]
291
301
public int [] searchRange(int [] nums, int target) {
292
302
int first = binarySearch(nums, target);
293
303
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)};
296
308
}
297
309
298
310
private int binarySearch(int [] nums, int target) {
299
311
int l = 0 , h = nums. length; // 注意 h 的初始值
300
312
while (l < h) {
301
313
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 ;
304
318
}
305
319
return l;
306
320
}
@@ -5914,6 +5928,64 @@ class MapSum {
5914
5928
5915
5929
## 图
5916
5930
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
+
5917
5989
### 拓扑排序
5918
5990
5919
5991
常用于在具有先序关系的任务规划中。
0 commit comments