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
2320class 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- # 无重复元素搜索时,更方便
5543class 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
7863class 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的解法
10890class 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
186168class 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
213196class 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
276234class 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
298254class 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
323276class 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
356303class 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