二分查找

69.求开方

69
由于x平方根的整数部分ans是满足k^2<=x的最大k值,因此我们可以对k进行二分查找。二分查找的下界为0,上界可以粗略地设定为x。在二分查找的每一步中,我们只需要比较中间元素mid的平方与x的大小关系,并通过比较的结果调整上下界的范围。

class Solution:
    def mySqrt(self, x: int) -> int:
        l,r,ans=0,x,-1
        while l<=r:
            m=(l+r)//2
            if m*m<=x:
                ans=m
                l=m+1
            else:
                r=m-1
        return ans

时间复杂度O(logx):二分查找需要的次数
空间复杂度O(1)

744.大于给定元素的最小元素

744
提示:
1.letters长度范围在[2, 10000]区间内。
2.letters 仅由小写字母组成,最少包含两个不同的字母。
3.目标字母target 是一个小写字母。

class Solution:
    def nextGreatestLetter(self, letters: List[str], target: str) -> str:
        l,r=0,len(letters)-1
        while l<r:  
            m=(r+l)//2
            if target>=letters[m]:#比目标值小,或等于目标值,那么往右找
                l=m+1
            else:#比目标值大,那么可能是要找的数
                r=m    
        return letters[l%len(letters)] #循环结束时,l指向数组中比目标字母大的最小字母
        #当目标字母比所有字母都大,l=len(letters),此时比目标字母大的最小字母,是第一个letters[0],所以需要求模,不能直接就L。     

540.有序数组中的单一元素

540
如果以遍历数组的方式,找那个不出现两次的数,时间复杂度是O(N),不符合题意O(logN)。所以这题可以用二分查找的方式:l,h指向数组的头尾位置,将其分为两个区间,通过下标奇偶关系判断,得出那个单一字符在哪一个区间,通过这种缩小区间的方法,就不用去判断另外一个区间。
index为单一元素在数组里的下标。
所以如果nums[m]==nums[m+1],那么index所在的数组位置为[m+2,h],此时令l=m+2;如果nums[m]!=nums[m+1],那么index所在的数组位置为[l,m],此时h=m(因为h的赋值表达式为h=m,那么循环条件也只能使用l<h这种形式,如果是h<=m,那么当h=m是继续判断,会进入死循环。要h<=m也行,那么赋值为h=m-1)。

class Solution:
    def singleNonDuplicate(self, nums: List[int]) -> int:
        l,h=0,len(nums)-1
        while l<h:
            m=(l+h)//2
            if m%2==1:#为了让m指向数组偶数下标
                m-=1
            if nums[m]==nums[m+1]:
                l=m+2
            else:
                h=m
        return nums[l]   

278.第一个错误版本

278
注意题意:true->错误版本,false->正确版本
例如[xxxxVVVVV]在这样一个有序数组里找到第一个V号。
二分搜索,不能写成mid=(l+r)/2,会溢出。要写成mid=l+(r-l)/2。
查找某一个值,如果找到返回其索引,如果找不到返回其本应该的位置(比如上面V号的位置)
如果第 m 个版本出错,则表示第一个错误的版本在 [l, m] 之间,令 h = m;否则第一个错误的版本在 [m + 1, h] 之间,令 l = m + 1。
因为 h 的赋值表达式为 h = m,因此循环条件为 l < h。

做到现在,感觉二分搜索的题目的代码都差不多呀!有模板。

# The isBadVersion API is already defined for you.
# @param version, an integer
# @return an integer
# def isBadVersion(version):

class Solution:
    def firstBadVersion(self, n):
        """
        :type n: int
        :rtype: int
        """
        l,r=1,n
        while l<r:
            mid=l+(r-l)//2
            if isBadVersion(mid):
                r=mid
            else:
                l=mid+1
        return l    

时间复杂度O(logN)
空间复杂度O(1)

153.旋转数组的最小数字

153
153

class Solution:
    def findMin(self, nums: List[int]) -> int:
        l,r=0,len(nums)-1
        while l<r:
            m=l+(r-l)//2
            if nums[m]>nums[r]:
                l=m+1
            else:
                r=m 
        return nums[l]

如果是直接调用函数,那么复杂度会比较高。
感觉还是套用二分查找的模板,但是那些边界条件是难点。
注意mid是与右边界比较而不是左边界!!!

一个网友说的编程技巧:
因为l<r所以最后一轮肯定是(r,r+1)
那么mid肯定是取值l 当判断条件是mid与l相比时 会出现与自身比造成等于情况 不好判断
所以判断条件时mid与r比 这样肯定是不同的两个数比

34.查找区间

34

这题其实就是在一个升序数组中,找目标值的下界和上界。
二分搜索思想虽然简单,但是细节难,搞错了就可能会死循环。
计算下边界时,当target<=nums[mid]时,right=mid-1;target>nums[mid]时,left=mid+1;返回left
计算上边界时,当target<nums[mid]时,right=mid-1;target>=nums[mid]时,left=mid+1;
返回right

写代码,分类讨论时,习惯的把闭/开区间注释在旁边,会比较好理解。

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        firstposition=Firstposition(nums,target)
        lastposition=Lastposition(nums,target)
        if lastposition<firstposition:# 查找,不存在
            return [-1,-1]
        return [firstposition,lastposition]    

def Firstposition(nums,target):
    left,right=0,len(nums)-1
    while left<=right:
        mid=left+(right-left)//2
        if target<=nums[mid]:
            #下界在左区间[left,mid-1]
            right=mid-1
        elif target>nums[mid]:
            #下界在右区间[mid+1,right]
            left=mid+1
    return left            

def Lastposition(nums,target):
    left,right=0,len(nums)-1
    while left<=right:
        mid=left+(right-left)//2
        if target<nums[mid]:
            #上界在左区间[left,mid-1]
            right=mid-1
        elif target>=nums[mid]:
            #上界在右区间[mid+1,right]
            left=mid+1
    return right            

最后,一个大佬对二分查找题目的总结一文带你搞定二分查找及其多个变种由浅入深,挺好的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值