二分查找算法及左右边界扩展


二分查找算法的各种扩展
二分查找是一个非常简单的算法,思路简洁容易实现,但是面对诸如查找左边界、右边界问题时,总是容易不小心写错边界条件。
二分查找有非常多的写法,并且在笔试面试中可能会遇到代码改错的题,如果对二分查找理解的深刻,很容易改错。这里主要记录一下自己对二分查找的理解,并提供了一个统一的代码框架。

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')
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值