leetcode二分搜索
「二分」一共有三类常见应用,分别是「整数二分」、「实数二分」以及「二分答案」,具体场景可以归为查找最小值、查找左边界、查找右边界等。
一、二分查找框架
def BinarySerach(nums: int, target:int) -> int:
left, right = 0, ...
while ...:
mid = left + (right - left) // 2 # 防止边界溢出
if nums[mid] == target:
...
elif nums[mid] < target:
...
elif nums[mid] > target:
...
return ...
这里用else if 表示出所有可能得情况方便理解。… 部分是二分查找中常会出现问题的地方。
二、基本二分——寻找一个数
通常场景是一个有序数组,具有一定单调性。目标为查找一个数,存在返回索引,若不存在则返回-1。
def BinartSearch(nums: int, target: int) ->int:
left, right = 0, len(nums - 1) # 注意
while left <= right: # 注意循环条件
mid = left + (left - right) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1 # 注意赋值情况
else:
right = mid - 1 # 注意
return -1
1、while 循环条件是 <= 而不是 <
这里将搜索区间(循环不变量)定义为闭区间[left, right],所以 right 初始化 len(nums)-1,即最后一个元素的索引。
此时停止搜索条件:
(1)找到目标值,即 nums[mid] == target,
(2)while循环终止仍没找到,返回-1。此时循环停止条件为left==right+1,区间为[rigth+1, right],显然不存在,终止循环。
注意,如果搜索区间定义为[left, right),那么right初始定义为len(nums),while循环为while left< right,终止条件为 left == right,区间为[right,right),区间非空,漏掉2,不能直接返回-1,需要进行判断:
#…
while left < right:
# …
return nums[left]==target ? left : -1
2、left与right更新值问题
当搜索区间为闭区间 [left, right]时,如果mid对应的值不是target,那么我们应当查找下一个,即[left, mid-1]或者[mid+1, right]。
三、寻找左侧边界的二分搜索
def leftBound(nums: int, target: int) -> int:
if len(nums) == 0:
return -1
left, right = 0, len(nums)
while left < right:
if nums[mid] == target:
right = mid
elif nums[mid] < target:
left = mid + 1
elif nums[mid] > target:
right = mid # 注意!
return left
1、while循环问题
这里定义的搜索区间为左开右闭区间[left, right),右边界right初始化为len(nums),所以当left==right时,搜索区间为[left, right),循环终止。
2、如果nums中没有target
函数的返回值(即 left 变量的值)取值区间是闭区间 [0, len(nums)],添加两行代码就能在正确的时候 return -1:
while left < right:
# ...
# target 比所有数都大
if left == len(nums):
return -1;
#类似之前算法的处理方式
return nums[left] == target ? left : -1
3、为什么是 left = mid + 1,right = mid ?
这里搜索区间为[left, right),所以当 nums[mid] 被找到之后,下一步的搜索区间应该去掉 mid 分割成两个区间,即 [left, mid) 或 [mid + 1, right)。
4、为什么该算法可以搜索左边界?
算法对于 nums[mid] == target 这种情况的处理:
if nums[mid] == target:
right = mid
找到 target 时缩小「搜索区间」的上界 right,在区间 [left, mid) 中继续搜索,即不断向左收缩直到查找到左边界。
5、返回值问题
其实返回left或right是一样的,因为while的终止条件是left==right。
三、查找右侧边界
与寻找左侧边界类似。
def rightBound(nums: int, target: int) -> int:
if len(nums) == 0:
return -1
left, right = 0, len(nums)
while left < right:
mid = left + (left + right) // 2;
if nums[mid] == target:
left = mid + 1 # 注意
elif nums[mid] < target:
left = mid + 1
elif nums[mid] > target:
right = mid
return left - 1 # 注意
1、为什么可以找到右边界?
if nums[mid] == target:
left = mid + 1
当 nums[mid] == target 时不返回,而是增大「搜索区间」的下界 left,使得区间不断向右收缩直到找到右侧边界。
2. 为什么最后返回 left - 1 ?而不是right?
while循环终止条件为 left == right,所以 left 和 right 是一样的,也可以写right-1。
减一是搜索右侧边界的特殊点,关键在于条件判断:
if nums[mid] == target:
left = mid + 1 # mid = left - 1
由于对 left 的更新是 left = mid + 1, while 循环结束时,nums[left] 一定不等于 target 了,而 nums[left - 1]可能是target。
3、如果 nums 中不存在 target 的处理
类似搜索左侧边界,因为left == right是while循环终止,left取值范围是[0, len(nums)],所以:
while left < right:
# ...
if left == 0:
return -1;
return nums[left-1] == target ? (left-1) : -1
四、总结
(1)最基本的二分查找算法:
因为初始化 right = len(nums) - 1
所以决定了「搜索区间」是 [left, right]
所以决定了 while(left <= right) 同时也决定了 left = mid+1 和 right = mid-1;因为只需找到一个 target 的索引即可
所以当 nums[mid] == target 时可以立即返回
(2)寻找左侧边界的二分查找:
因为初始化 right = len(nums)
所以决定了的「搜索区间」是 [left, right)
所以决定了 while (left < right)
同时也决定了 left = mid+1 和 right = mid;
因为需找到 target 的最左侧索引
所以当 nums[mid] == target 时不要立即返回
而要收紧右侧边界以锁定左侧边界
(3)寻找右侧边界的二分查找:
因为初始化right = len(nums)
所以决定了「搜索区间」是[left, right)
所以 while(left < right)
同时 left = mid +1 right = mid
所以当 nums[mid]==target时不立即返回
而是要收紧左侧以逼近右侧边界
如有纰漏,敬请指正~
原文:
https://www.cnblogs.com/kyoner/p/11080078.html