Skip to content

Commit a41eeda

Browse files
committed
(l + r) // 2 to l + (r - l) // 2; improved doc; added hidden binary search problems
1 parent b77a5b7 commit a41eeda

File tree

1 file changed

+56
-87
lines changed

1 file changed

+56
-87
lines changed

basic_algorithm/binary_search.md

+56-87
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,29 @@
11
# 二分搜索
22

3-
## 二分搜索模板
3+
给一个**有序数组**和目标值,找第一次/最后一次/任何一次出现的索引,时间复杂度 O(logN)。
44

5-
给一个**有序数组**和目标值,找第一次/最后一次/任何一次出现的索引,如果没有出现返回-1
5+
## 模板
66

7-
模板四点要素
7+
常用的二分搜索模板有如下三种形式:
88

9-
- 1、初始化:start=0、end=len-1
10-
- 2、循环退出条件:start + 1 < end
11-
- 3、比较中点和目标值:A[mid] ==、 <、> target
12-
- 4、判断最后两个元素是否符合:A[start]、A[end] ? target
13-
14-
时间复杂度 O(logn),使用场景一般是有序数组的查找
9+
![binary_search_template](https://img.fuiboom.com/img/binary_search_template.png)
1510

16-
典型示例
11+
其中,模板 1 和 3 是最常用的,几乎所有二分查找问题都可以用其中之一轻松实现。模板 2 更高级一些,用于解决某些类型的问题。详细的对比可以参考 Leetcode 上的文章:[二分搜索模板](https://leetcode-cn.com/explore/learn/card/binary-search/212/template-analysis/847/)
1712

1813
### [binary-search](https://leetcode-cn.com/problems/binary-search/)
1914

2015
> 给定一个  n  个元素有序的(升序)整型数组  nums 和一个目标值  target  ,写一个函数搜索  nums  中的 target,如果目标值存在返回下标,否则返回 -1。
2116
17+
- 模板 3 的实现
18+
2219
```Python
2320
class Solution:
2421
def search(self, nums: List[int], target: int) -> int:
2522

2623
l, r = 0, len(nums) - 1
2724

2825
while l + 1 < r:
29-
mid = (l + r) // 2
26+
mid = l + (r - l) // 2
3027
if nums[mid] < target:
3128
l = mid
3229
else:
@@ -40,25 +37,16 @@ class Solution:
4037
return -1
4138
```
4239

43-
大部分二分查找类的题目都可以用这个模板,然后做一点特殊逻辑即可
44-
45-
另外二分查找还有一些其他模板如下图,大部分场景模板 3 都能解决问题,而且还能找第一次/最后一次出现的位置,应用更加广泛
46-
47-
![binary_search_template](https://img.fuiboom.com/img/binary_search_template.png)
48-
49-
所以用模板#3 就对了,详细的对比可以这边文章介绍:[二分搜索模板](https://leetcode-cn.com/explore/learn/card/binary-search/212/template-analysis/847/)
50-
51-
- 如果是最简单的二分搜索,不需要找第一个、最后一个位置、或者是没有重复元素,可以使用模板 1,代码更简洁
40+
- 如果是最简单的二分搜索,不需要找第一个、最后一个位置,或者是没有重复元素,可以使用模板 1,代码更简洁。同时,如果搜索失败,left 是第一个大于 target 的索引,right 是最后一个小于 target 的索引。
5241

5342
```Python
54-
# 无重复元素搜索时,更方便
5543
class Solution:
5644
def search(self, nums: List[int], target: int) -> int:
5745

5846
l, r = 0, len(nums) - 1
5947

6048
while l <= r:
61-
mid = (l + r) // 2
49+
mid = l + (r - l) // 2
6250
if nums[mid] == target:
6351
return mid
6452
elif nums[mid] > target:
@@ -67,12 +55,9 @@ class Solution:
6755
l = mid + 1
6856

6957
return -1
70-
# 如果找不到,start 是第一个大于target的索引
71-
# 如果在B+树结构里面二分搜索,可以return start
72-
# 这样可以继续向子节点搜索,如:node:=node.Children[start]
7358
```
7459

75-
- 模板 2
60+
- 模板 2 的实现
7661

7762
```Python
7863
class Solution:
@@ -81,7 +66,7 @@ class Solution:
8166
l, r = 0, len(nums) - 1
8267

8368
while l < r:
84-
mid = (l + r) // 2
69+
mid = l + (r - l) // 2
8570
if nums[mid] < target:
8671
l = mid + 1
8772
else:
@@ -93,18 +78,15 @@ class Solution:
9378
return -1
9479
```
9580

96-
97-
9881
## 常见题目
9982

10083
### [find-first-and-last-position-of-element-in-sorted-array](https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/)
10184

10285
> 给定一个包含 n 个整数的排序数组,找出给定目标值 target 的起始和结束位置。如果目标值不在数组中,则返回`[-1, -1]`
10386
104-
- 思路:核心点就是找第一个 target 的索引,和最后一个 target 的索引,所以用两次二分搜索分别找第一次和最后一次的位置
87+
- 思路:核心点就是找第一个 target 的索引,和最后一个 target 的索引,所以用两次二分搜索分别找第一次和最后一次的位置,下面是使用模板 3 的解法
10588

10689
```Python
107-
# 使用模板3的解法
10890
class Solution:
10991
def searchRange(self, nums, target):
11092
Range = [-1, -1]
@@ -113,7 +95,7 @@ class Solution:
11395

11496
l, r = 0, len(nums) - 1
11597
while l + 1 < r:
116-
mid = (l + r) // 2
98+
mid = l + (r - l) // 2
11799
if nums[mid] < target:
118100
l = mid
119101
else:
@@ -128,7 +110,7 @@ class Solution:
128110

129111
l, r = 0, len(nums) - 1
130112
while l + 1 < r:
131-
mid = (l + r) // 2
113+
mid = l + (r - l) // 2
132114
if nums[mid] <= target:
133115
l = mid
134116
else:
@@ -153,7 +135,7 @@ class Solution:
153135

154136
l, r = 0, len(nums) - 1
155137
while l < r:
156-
mid = (l + r) // 2
138+
mid = l + (r - l) // 2
157139
if nums[mid] < target:
158140
l = mid + 1
159141
else:
@@ -166,7 +148,7 @@ class Solution:
166148

167149
l, r = 0, len(nums) - 1
168150
while l < r:
169-
mid = (l + r + 1) // 2
151+
mid = l + (r - l + 1) // 2
170152
if nums[mid] > target:
171153
r = mid - 1
172154
else:
@@ -180,7 +162,7 @@ class Solution:
180162

181163
> 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
182164
183-
思路:使用模板 1,若不存在,随后的左边界为第一个大于目标值的索引(插入位置),右边界为最后一个小于目标值的索引
165+
- 使用模板 1,若不存在,左边界为第一个大于目标值的索引(插入位置),右边界为最后一个小于目标值的索引
184166

185167
```Python
186168
class Solution:
@@ -189,7 +171,7 @@ class Solution:
189171
l, r = 0, len(nums) - 1
190172

191173
while l <= r:
192-
mid = (l + r) // 2
174+
mid = l + (r - l) // 2
193175
if nums[mid] == target:
194176
return mid
195177
elif nums[mid] > target:
@@ -204,10 +186,11 @@ class Solution:
204186

205187
> 编写一个高效的算法来判断  m x n  矩阵中,是否存在一个目标值。该矩阵具有如下特性:
206188
>
207-
> - 每行中的整数从左到右按升序排列。
208-
> - 每行的第一个整数大于前一行的最后一个整数。
189+
> 1. 每行中的整数从左到右按升序排列。
190+
>
191+
> 2. 每行的第一个整数大于前一行的最后一个整数。
209192
210-
思路:两次二分,首先定位行数,接着定位列数
193+
- 两次二分,首先定位行数,接着定位列数
211194

212195
```Python
213196
class Solution:
@@ -219,7 +202,7 @@ class Solution:
219202
l, r = 0, len(matrix) - 1
220203

221204
while l <= r:
222-
mid = (l + r) // 2
205+
mid = l + (r - l) // 2
223206
if matrix[mid][0] == target:
224207
return True
225208
elif matrix[mid][0] < target:
@@ -230,7 +213,7 @@ class Solution:
230213
row = r
231214
l, r = 0, len(matrix[0]) - 1
232215
while l <= r:
233-
mid = (l + r) // 2
216+
mid = l + (r - l) // 2
234217
if matrix[row][mid] == target:
235218
return True
236219
elif matrix[row][mid] < target:
@@ -241,36 +224,11 @@ class Solution:
241224
return False
242225
```
243226

244-
### [first-bad-version](https://leetcode-cn.com/problems/first-bad-version/)
245-
246-
> 假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。
247-
> 你可以通过调用  bool isBadVersion(version)  接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。
248-
249-
```Python
250-
class Solution:
251-
def firstBadVersion(self, n):
252-
253-
l, r = 1, n
254-
255-
while l + 1 < r:
256-
mid = (l + r) // 2
257-
if isBadVersion(mid):
258-
r = mid
259-
else:
260-
l = mid
261-
262-
if isBadVersion(l):
263-
return l
264-
else:
265-
return r
266-
```
267-
268227
### [find-minimum-in-rotated-sorted-array](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/)
269228

270-
> 假设按照升序排序的数组在预先未知的某个点上进行了旋转( 例如,数组  [0,1,2,4,5,6,7] 可能变为  [4,5,6,7,0,1,2] )。
271-
> 请找出其中最小的元素。假设数组中无重复元素。
229+
> 假设按照升序排序的数组在预先未知的某个点上进行了旋转,例如,数组 [0, 1, 2, 4, 5, 6, 7] 可能变为 [4, 5, 6, 7, 0, 1, 2]。请找出其中最小的元素。假设数组中无重复元素。
272230
273-
思路:使用二分搜索,当中间元素大于右侧元素时意味着拐点即最小元素在右侧,否则在左侧
231+
- 使用二分搜索,当中间元素大于右侧元素时意味着拐点即最小元素在右侧,否则在左侧
274232

275233
```Python
276234
class Solution:
@@ -279,7 +237,7 @@ class Solution:
279237
l , r = 0, len(nums) - 1
280238

281239
while l < r:
282-
mid = (l + r) // 2
240+
mid = l + (r - l) // 2
283241
if nums[mid] > nums[r]: # 数组有重复时,若 nums[l] == nums[mid] == nums[r],无法判断移动方向
284242
l = mid + 1
285243
else:
@@ -290,9 +248,7 @@ class Solution:
290248

291249
### [find-minimum-in-rotated-sorted-array-ii](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array-ii/)
292250

293-
> 假设按照升序排序的数组在预先未知的某个点上进行了旋转
294-
> ( 例如,数组  [0,1,2,4,5,6,7] 可能变为  [4,5,6,7,0,1,2] )。
295-
> 请找出其中最小的元素。(包含重复元素)
251+
> 假设按照升序排序的数组在预先未知的某个点上进行了旋转,例如,数组 [0, 1, 2, 4, 5, 6, 7] 可能变为 [4, 5, 6, 7, 0, 1, 2]。请找出其中最小的元素。数组中可能包含重复元素。
296252
297253
```Python
298254
class Solution:
@@ -301,7 +257,7 @@ class Solution:
301257
l , r = 0, len(nums) - 1
302258

303259
while l < r:
304-
mid = (l + r) // 2
260+
mid = l + (r - l) // 2
305261
if nums[mid] > nums[r]:
306262
l = mid + 1
307263
elif nums[mid] < nums[r] or nums[mid] != nums[l]:
@@ -314,10 +270,7 @@ class Solution:
314270

315271
### [search-in-rotated-sorted-array](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/)
316272

317-
> 假设按照升序排序的数组在预先未知的某个点上进行了旋转。
318-
> ( 例如,数组  [0,1,2,4,5,6,7]  可能变为  [4,5,6,7,0,1,2] )。
319-
> 搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回  -1 。
320-
> 你可以假设数组中不存在重复的元素。
273+
> 假设按照升序排序的数组在预先未知的某个点上进行了旋转,例如,数组 [0, 1, 2, 4, 5, 6, 7] 可能变为 [4, 5, 6, 7, 0, 1, 2]。搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回  -1。假设数组中不存在重复的元素。
321274
322275
```Python
323276
class Solution:
@@ -326,7 +279,7 @@ class Solution:
326279
l , r = 0, len(nums) - 1
327280

328281
while l <= r:
329-
mid = (l + r) // 2
282+
mid = l + (r - l) // 2
330283
if nums[mid] == target:
331284
return mid
332285
elif nums[mid] > target:
@@ -342,15 +295,9 @@ class Solution:
342295
return -1
343296
```
344297

345-
注意点
346-
347-
> 面试时,可以直接画图进行辅助说明,空讲很容易让大家都比较蒙圈
348-
349298
### [search-in-rotated-sorted-array-ii](https://leetcode-cn.com/problems/search-in-rotated-sorted-array-ii/)
350299

351-
> 假设按照升序排序的数组在预先未知的某个点上进行了旋转。
352-
> ( 例如,数组  [0,0,1,2,2,5,6]  可能变为  [2,5,6,0,0,1,2] )。
353-
> 编写一个函数来判断给定的目标值是否存在于数组中。若存在返回  true,否则返回  false。(包含重复元素)
300+
> 假设按照升序排序的数组在预先未知的某个点上进行了旋转,例如,数组 [0, 0, 1, 2, 2, 5, 6] 可能变为 [2, 5, 6, 0, 0, 1, 2]。编写一个函数来判断给定的目标值是否存在于数组中,若存在返回  true,否则返回  false。数组中可能包含重复元素。
354301
355302
```Python
356303
class Solution:
@@ -363,7 +310,7 @@ class Solution:
363310
l += 1
364311
r -= 1
365312
continue
366-
mid = (l + r) // 2
313+
mid = l + (r - l) // 2
367314
if nums[mid] == target:
368315
return True
369316
elif nums[mid] > target:
@@ -379,6 +326,28 @@ class Solution:
379326
return False
380327
```
381328

329+
## 隐含的二分搜索
330+
331+
有时用到二分搜索的题目并不会直接给你一个有序数组,它隐含在题目中,需要你去发现或者构造。一类常见的隐含的二分搜索的问题是求某个有界数据的最值,以最小值为例,当数据比最小值大时都符合条件,比最小值小时都不符合条件,那么符合/不符合条件就构成了一种有序关系,再加上数据有界,我们就可以使用二分搜索来找数据的最小值。注意,数据的界一般也不会在题目中明确提示你,需要你自己去发现。
332+
333+
### [koko-eating-bananas](https://leetcode-cn.com/problems/koko-eating-bananas/)
334+
335+
```Python
336+
class Solution:
337+
def minEatingSpeed(self, piles: List[int], H: int) -> int:
338+
339+
l, r = 1, max(piles)
340+
341+
while l < r:
342+
mid = l + (r - l) // 2
343+
if sum([-pile // mid for pile in piles]) < -H:
344+
l = mid + 1
345+
else:
346+
r = mid
347+
348+
return l
349+
```
350+
382351
## 总结
383352

384353
二分搜索核心四点要素(必背&理解)

0 commit comments

Comments
 (0)