文章目录
二分查找算法的各种扩展
二分查找是一个非常简单的算法,思路简洁容易实现,但是面对诸如查找左边界、右边界问题时,总是容易不小心写错边界条件。
二分查找有非常多的写法,并且在笔试面试中可能会遇到代码改错的题,如果对二分查找理解的深刻,很容易改错。这里主要记录一下自己对二分查找的理解,并提供了一个统一的代码框架。
leetcode 704. 二分查找
https://leetcode-cn.com/problems/binary-search/
leetcode 34. 在排序数组中查找元素的第一个和最后一个位置
https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/
leetcode 315.计算右侧小于当前元素的个数
https://leetcode-cn.com/problems/count-of-smaller-numbers-after-self/comments/
问题1:不重复数组中二分查找
在写二分查找代码时,一定要非常细心,建议将else分支的条件写详细。
对于不重复的数组,或者说没有要求返回最左边or最右边的位置,这是最基本的问题,也是最简单省心的问题。
简洁版本:
class Solution(object):
def search(self, nums, target):
low,high=0,len(nums)-1
# 注意1:这里有等于号,原因是当low,high被更新后,会出现low==high的情况,这时候自然可以退出循环了,
# 但是最后一步需要判断是否nums[low]==target,方法就是再进入一次循环;
while low<=high:
mid=(low+high)//2
if nums[mid]==target:
return mid
elif nums[mid]<target: # 将else分支的条件写详细
low=mid+1
elif nums[mid]>target:
high=mid-1
return -1
当然如果不想使用low<=high,希望和下面的问题2,3模板保持一致,可以写成low<high,然后在while外面在进行一次判断。代码如下:
统一框架:
class Solution(object):
def search(self, nums, target):
low,high=0,len(nums)-1
while low<high:
mid=(low+high)//2
if nums[mid]==target:
return mid
elif nums[mid]<target:
low=mid+1
elif nums[mid]>target:
high=mid-1
if nums[low]==target:
return low
return -1
问题2:二分查找并返回第一次出现(左边界)的位置
简洁版本:
class Solution(object):
def search(self, nums, target):
low,high=0,len(nums)-1
while low<high: #注意:和问题的第二种写法一致
mid=(low+high)//2 #注意,这里取偏左的中值
#当找到目标的时候,保持右侧high指向target,然后low继续逼近high,最终将low和high将在最左侧的target位置相遇
#当没有找到目标的且 nums[mid]>target,保持high指向更高的值。
if nums[mid]>=target:
high=mid
elif nums[mid]<target:
low=mid+1
if nums[low]==target:
return low
return -1
统一框架:
class Solution(object):
def search(self, nums, target):
low,high=0,len(nums)-1
while low<high:
mid=(low+high)//2
if nums[mid]==target:
high=mid
elif nums[mid]<target:
low=mid+1
elif nums[mid]>target:
high=mid-1
if nums[low]==target:
return low
return -1
问题3:二分查找并返回最后一次出现(右边界)的位置
简洁版本:
class Solution(object):
def search(self, nums, target):
low,high=0,len(nums)-1
while low<high: #注意:和问题的第二种写法一致
mid=(low+high+1)//2 #注意,这里取偏右的中值,【防止死循环】
#当找到目标的时候,保持左侧low指向target,然后high继续逼近low,最终将low和high将在最右侧的target位置相遇
#当没有找到目标的且 nums[mid]<target,保持low指向更低的值。
if nums[mid]<=target:
low=mid
elif nums[mid]>target:
high=mid-1
if nums[low]==target:
return low
return -1
为什么mid=(low+high+1)//2
可以防止死循环,因为当low+high为奇数时,mid=(low+high)//2仍是奇数,可能会出现low=(low+high)//2的情况,导致死循环。例如low=1,high=2,mid=1。
统一框架:
class Solution(object):
def search(self, nums, target):
low,high=0,len(nums)-1
while low<high:
mid=(low+high+1)//2
if nums[mid]==target:
low=mid
elif nums[mid]<target:
low=mid+1
elif nums[mid]>target:
high=mid-1
if nums[low]==target:
return low
return -1
问题4:二分查找最左侧插入位置,实现bisect.bisec_left()
输入一个已排序数组(从小到大),查找给定数字的最左侧的插入位置
def bisection_left(nums,t):
if len(nums)==0: return 0
l,h=0,len(nums)-1
while l<h:
m=(l+h)//2
if nums[m]>=t:
h=m
elif nums[m]<t:
l=m+1
if nums[l]>=t:
return l
else:
return l+1
# 测试
import bisect
nums=[1,2,3]
t=2
if bisect.bisect_left(nums,t)==bisection_left(nums,t):
print('True')
问题5:二分查找最右侧插入位置,实现bisect.bisec_right()
输入一个已排序数组(从小到大),查找给定数字的最右侧的插入位置
def bisection_right(nums, t):
if len(nums)==0: return 0
l, h = 0, len(nums) - 1
while l < h:
m = (l + h + 1) // 2
if nums[m] > t:
h = m - 1
elif nums[m] <= t:
l = m
if nums[l] <= t:
return l+1
else:
return l
# 测试
import bisect
nums=[1,2,3]
t=2
if bisect.bisect_right(nums,t)==bisection_right(nums,t):
print('True')