54
54
## 二分查找
55
55
56
56
``` java
57
- public int search (int key, int [] array ) {
58
- int l = 0 , h = array . length - 1 ;
57
+ public int binarySearch (int key, int [] nums ) {
58
+ int l = 0 , h = nums . length - 1 ;
59
59
while (l <= h) {
60
60
int mid = l + (h - l) / 2 ;
61
- if (key == array [mid]) return mid;
62
- if (key < array [mid]) h = mid - 1 ;
61
+ if (key == nums [mid]) return mid;
62
+ if (key < nums [mid]) h = mid - 1 ;
63
63
else l = mid + 1 ;
64
64
}
65
65
return - 1 ;
66
66
}
67
67
```
68
68
69
- 实现时需要注意以下细节:
69
+ ** 时间复杂度**
70
+
71
+ O(logN)
72
+
73
+ ** 计算 mid**
74
+
75
+ 在计算 mid 时不能使用 mid = (l + h) / 2 这种方式,因为 l + h 可能会导致加法溢出,应该使用 mid = l + (h - l) / 2。
76
+
77
+ ** 计算 h**
70
78
71
- - 在计算 mid 时不能使用 mid = (l + h) / 2 这种方式,因为 l + h 可能会导致加法溢出,应该使用 mid = l + (h - l) / 2 。
79
+ 当循环条件为 l <= h,则 h = mid - 1。因为如果 h = mid,会出现循环无法退出的情况,例如 l = 1,h = 1,此时 mid 也等于 1,如果此时继续执行 h = mid,那么就会无限循环 。
72
80
73
- - 对 h 的赋值和循环条件有关, 当循环条件为 l <= h 时,h = mid - 1;当循环条件为 l < h 时,h = mid。解释如下:在循环条件为 l <= h 时,如果 h = mid,会出现循环无法退出的情况,例如 l = 1,h = 1,此时 mid 也等于 1,如果此时继续执行 h = mid,那么就会无限循环;在循环条件为 l < h,如果 h = mid - 1,会错误跳过查找的数,例如对于数组 [ 1,2,3] ,要查找 1,最开始 l = 0,h = 2,mid = 1,判断 key < arr[ mid] 执行 h = mid - 1 = 0,此时循环退出,直接把查找的数跳过了。
81
+ 当循环条件为 l < h,则 h = mid。因为如果 h = mid - 1,会错误跳过查找的数,例如对于数组 [ 1,2,3] ,要查找 1,最开始 l = 0,h = 2,mid = 1,判断 key < arr[ mid] 执行 h = mid - 1 = 0,此时循环退出,直接把查找的数跳过了。
74
82
75
- - l 的赋值一般都为 l = mid + 1。
83
+ ** 返回值**
84
+
85
+ 在循环条件为 l <= h 的情况下,循环退出时 l 总是比 h 大 1,并且 l 是将 key 插入 nums 中的正确位置。例如对于 nums = {0,1,2,3},key = 4,循环退出时 l = 4,将 key 插入到 nums 中的第 4 个位置就能保持 nums 有序的特点。
86
+
87
+ 在循环条件为 l < h 的情况下,循环退出时 l 和 h 相等。
88
+
89
+ 如果只是想知道 key 存不存在,在循环退出之后可以直接返回 -1 表示 key 不存在于 nums 中。
76
90
77
91
** 求开方**
78
92
@@ -87,17 +101,19 @@ Output: 2
87
101
Explanation: The square root of 8 is 2.82842..., and since we want to return an integer, the decimal part will be truncated.
88
102
```
89
103
90
- 一个数 x 的开方 sqrt 一定在 0 \~ x 之间,并且满足 sqrt == x / sqrt 。可以利用二分查找在 0 \~ x 之间查找 sqrt。
104
+ 一个数 x 的开方 sqrt 一定在 0 \~ x 之间,并且满足 sqrt == x / sqrt。可以利用二分查找在 0 \~ x 之间查找 sqrt。
105
+
106
+ 对于 x = 8,它的开方是 2.82842...,最后应该返回 2 而不是 3。在循环条件为 l <= h 并且循环退出时,h 总是比 l 小 1,也就是说 h = 2,l = 3,因此最后的返回值应该为 h 而不是 l。
91
107
92
108
``` java
93
109
public int mySqrt(int x) {
94
- if (x <= 1 ) return x;
110
+ if (x <= 1 ) return x;
95
111
int l = 1 , h = x;
96
- while (l <= h){
112
+ while (l <= h) {
97
113
int mid = l + (h - l) / 2 ;
98
114
int sqrt = x / mid;
99
- if (sqrt == mid) return mid;
100
- else if (sqrt < mid) h = mid - 1 ;
115
+ if (sqrt == mid) return mid;
116
+ if (sqrt < mid) h = mid - 1 ;
101
117
else l = mid + 1 ;
102
118
}
103
119
return h;
@@ -120,23 +136,25 @@ Because the 4th row is incomplete, we return 3.
120
136
121
137
题目描述:第 i 行摆 i 个,统计能够摆的行数。
122
138
123
- 返回 h 而不是 l,因为摆的硬币最后一行不能算进去。
139
+ n 个硬币能够摆的行数 row 在 0 \~ n 之间,并且满足 n == row * (row + 1) / 2,因此可以利用二分查找在 0 \~ n 之间查找 row。
140
+
141
+ 对于 n = 8,它能摆的行数 row = 3,这是因为最后没有摆满的那一行不能算进去,因此在循环退出时应该返回 h。
124
142
125
143
``` java
126
144
public int arrangeCoins(int n) {
127
145
int l = 0 , h = n;
128
- while (l <= h){
129
- int m = l + (h - l) / 2 ;
130
- long x = m * (m + 1 ) / 2 ;
131
- if (x == n) return m ;
132
- else if (x < n) l = m + 1 ;
133
- else h = m - 1 ;
146
+ while (l <= h) {
147
+ int mid = l + (h - l) / 2 ;
148
+ long x = mid * (mid + 1 ) / 2 ;
149
+ if (x == n) return mid ;
150
+ else if (x < n) l = mid + 1 ;
151
+ else h = mid - 1 ;
134
152
}
135
153
return h;
136
154
}
137
155
```
138
156
139
- 可以不用二分查找 ,更直观的解法如下:
157
+ 本题可以不用二分查找 ,更直观的解法如下:
140
158
141
159
``` java
142
160
public int arrangeCoins(int n) {
@@ -149,6 +167,39 @@ public int arrangeCoins(int n) {
149
167
}
150
168
```
151
169
170
+ ** 大于给定元素的最小元素**
171
+
172
+ [ Leetcode : 744. Find Smallest Letter Greater Than Target (Easy)] ( https://leetcode.com/problems/find-smallest-letter-greater-than-target/description/ )
173
+
174
+ ``` html
175
+ Input:
176
+ letters = ["c", "f", "j"]
177
+ target = "d"
178
+ Output: "f"
179
+
180
+ Input:
181
+ letters = ["c", "f", "j"]
182
+ target = "k"
183
+ Output: "c"
184
+ ```
185
+
186
+ 题目描述:给定一个有序的字符数组 letters 和一个字符 target,要求找出 letters 中大于 target 的最小字符。letters 字符数组是循环数组。
187
+
188
+ 应该注意最后返回的是 l 位置的字符。
189
+
190
+ ``` java
191
+ public char nextGreatestLetter(char [] letters, char target) {
192
+ int n = letters. length;
193
+ int l = 0 , h = n - 1 ;
194
+ while (l <= h) {
195
+ int m = l + (h - l) / 2 ;
196
+ if (letters[m] <= target) l = m + 1 ;
197
+ else h = m - 1 ;
198
+ }
199
+ return l < n ? letters[l] : letters[0 ];
200
+ }
201
+ ```
202
+
152
203
** 有序数组的 Single Element**
153
204
154
205
[ Leetcode : 540. Single Element in a Sorted Array (Medium)] ( https://leetcode.com/problems/single-element-in-a-sorted-array/description/ )
@@ -158,21 +209,101 @@ Input: [1,1,2,3,3,4,4,8,8]
158
209
Output: 2
159
210
```
160
211
161
- 题目描述:一个有序数组只有一个数不出现两次,找出这个数。
212
+ 题目描述:一个有序数组只有一个数不出现两次,找出这个数。要求以 O(logN) 时间复杂度进行求解。
213
+
214
+ 令 key 为 Single Element 在数组中的位置。如果 m 为偶数,并且 m < key,那么 nums[ m] == nums[ m + 1] ;m >= key,那么 nums[ m] != nums[ m + 1] 。
215
+
216
+ 从上面的规律可以知道,如果 nums[ m] == nums[ m + 1] ,那么 key 所在的数组位置为 m + 2 \~ n - 1,此时令 l = m + 2;如果 nums[ m] != nums[ m + 1] ,那么 key 所在的数组位置为 0 \~ m,此时令 h = m。
217
+
218
+ 因为 h 的赋值表达式为 h = m,那么循环条件也就只能使用 l < h 这种形式。
162
219
163
220
``` java
164
221
public int singleNonDuplicate(int [] nums) {
165
222
int l = 0 , h = nums. length - 1 ;
166
- while (l < h) {
223
+ while (l < h) {
167
224
int m = l + (h - l) / 2 ;
168
- if (m % 2 == 1 ) m-- ; // 保证 l/h/m 都在偶数位,使得查找区间大小一直都是奇数
169
- if (nums[m] == nums[m + 1 ]) l = m + 2 ;
225
+ if (m % 2 == 1 ) m-- ; // 保证 l/h/m 都在偶数位,使得查找区间大小一直都是奇数
226
+ if (nums[m] == nums[m + 1 ]) l = m + 2 ;
170
227
else h = m;
171
228
}
172
229
return nums[l];
173
230
}
174
231
```
175
232
233
+ ** 第一个错误的版本**
234
+
235
+ [ Leetcode : 278. First Bad Version (Easy)] ( https://leetcode.com/problems/first-bad-version/description/ )
236
+
237
+ 题目描述:给定一个元素 n 代表有 [ 1, 2, ..., n] 版本,可以调用 isBadVersion(int x) 知道某个版本是否错误,要求找到第一个错误的版本。
238
+
239
+ 如果第 m 个版本出错,则表示第一个错误的版本在 1 \~ m 之前,令 h = m;否则第一个错误的版本在 m + 1 \~ n 之间,令 l = m + 1。
240
+
241
+ 因为 h 的赋值表达式为 h = m,因此循环条件为 l < h。
242
+
243
+ ``` java
244
+ public int firstBadVersion(int n) {
245
+ int l = 1 , h = n;
246
+ while (l < h) {
247
+ int m = l + (h - l) / 2 ;
248
+ if (isBadVersion(m)) h = m;
249
+ else l = m + 1 ;
250
+ }
251
+ return l;
252
+ }
253
+ ```
254
+
255
+ ** 旋转数组的最小数字**
256
+
257
+ [ Leetcode : 153. Find Minimum in Rotated Sorted Array (Medium)] ( https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/description/ )
258
+
259
+ ``` html
260
+ Input: [3,4,5,1,2],
261
+ Output: 1
262
+ ```
263
+
264
+ ``` java
265
+ public int findMin(int [] nums) {
266
+ int l = 0 , h = nums. length;
267
+ while (l < h) {
268
+ int m = l + (h - l) / 2 ;
269
+ if (nums[m] <= nums[h]) h = m;
270
+ else l = m + 1 ;
271
+ }
272
+ return nums[l];
273
+ }
274
+ ```
275
+
276
+ ** 查找区间**
277
+
278
+ [ Leetcode : 34. Search for a Range (Medium)] ( https://leetcode.com/problems/search-for-a-range/description/ )
279
+
280
+ ``` html
281
+ Input: nums = [5,7,7,8,8,10], target = 8
282
+ Output: [3,4]
283
+
284
+ Input: nums = [5,7,7,8,8,10], target = 6
285
+ Output: [-1,-1]
286
+ ```
287
+
288
+ ``` java
289
+ public int [] searchRange(int [] nums, int target) {
290
+ int first = binarySearch(nums, target);
291
+ int last = binarySearch(nums, target + 1 ) - 1 ;
292
+ if (first == nums. length || nums[first] != target) return new int []{- 1 , - 1 };
293
+ return new int []{first, Math . max(first, last)};
294
+ }
295
+
296
+ private int binarySearch(int [] nums, int target) {
297
+ int l = 0 , h = nums. length; // 注意 h 的初始值
298
+ while (l < h) {
299
+ int m = l + (h - l) / 2 ;
300
+ if (nums[m] >= target) h = m;
301
+ else l = m + 1 ;
302
+ }
303
+ return l;
304
+ }
305
+ ```
306
+
176
307
## 贪心思想
177
308
178
309
贪心思想保证每次操作都是局部最优的,并且最后得到的结果是全局最优的。
@@ -185,7 +316,7 @@ public int singleNonDuplicate(int[] nums) {
185
316
Input: [1,2], [1,2,3]
186
317
Output: 2
187
318
188
- Explanation: You have 2 children and 3 cookies. The greed factors of 2 children are 1, 2.
319
+ Explanation: You have 2 children and 3 cookies. The greed factors of 2 children are 1, 2.
189
320
You have 3 cookies and their sizes are big enough to gratify all of the children,
190
321
You need to output 2.
191
322
```
0 commit comments