leetcode2021年度刷题分类型总结(二)二分法 (python)

在使用二分法的过程中,最让我迷惑的就是边界的界定,这里总结一下使用二分法时,可行的边界条件

例一:704. 二分查找

704. 二分查找

版本一:(错误版本)

这题是一道非常简单的二分查找题,但是我却并没有一次做对,原因出在没有处理好退出二分查找的边界导致超出时间限制的错误。以我最后出错的测试用例来分析,在【2,5】数组中,由于我的while循环退出条件是left<=right,所以只有当left>right才会退出循环,但是在我的代码中,当nums[mid]>target和nums[mid]<target时直接将mid赋值给right和left,且mid的更新式子为mid=(left+right)//2,导致在【2,5】数组中mid一直为零。而由于target为5,需要输出的序号为1,所以整个程序卡在了while循环里。
正确的版本介绍在版本二、版本三

class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        n=len(nums)
        left,right=0,n-1
        if target not in nums:
            return -1

        while left<=right:
            mid=(left+right)//2
            if nums[mid]==target:
                return mid
            elif nums[mid]>target:
                right=mid
            else:
                left=mid

在这里插入图片描述

版本二:(正确版本)

相较版本一,对边界条件稍微修改,while退出循环的判断条件不变,还是是left>right退出循环。但是修改nums[mid]<target和nums[mid]>target时left和right的赋值。right=mid-1和left=mid+1。还是以【2,5】数组、target=5为例,最后left==right且都为1,mid也等于1,这时的代码逻辑可以得到正确的结果

class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        
        left,right=0,len(nums)-1
        if target not in nums:
            return -1

        while left<=right:
            mid=(left+right)//2
            if nums[mid]==target:
                return mid
            elif nums[mid]>target:
                right=mid-1
            else:
                left=mid+1

在这里插入图片描述

版本三:(正确版本)

除了以上两种情况之外,还有一种可以得到正确结果的情况。这种情况下,假设right所指向的数是取不到的,所以初始化为len(nums),更新时把已经判断过不是target的mid直接赋给right。

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        left,right  =0, len(nums)
        while left < right:
            mid = (left + right) // 2
            if nums[mid] < target:
                left = mid+1
            elif nums[mid] > target:
                right = mid
            else:
                return mid
        return -1

在这里插入图片描述

例二:162. 寻找峰值

162. 寻找峰值
这题要求使用时间复杂度为 O(log n) 的算法来解决此问题,需要用到二分查找,但是题目里给出的数组并不是有序数组,所以比较难想出解决方法。
最重要的点在于理解,由于题目中给出了nums[-1] = nums[n] = -∞,可以形象地想象成数组中首尾都是洼地,所以只要数组中存在一个元素比相邻元素大,那么沿着它一定可以找到一个峰值。这里mid和mid+1用来表示数组中的某个元素和它相邻的元素。
如果nums[mid]>nums[mid+1],说明在数组的左边一定存在一个峰值,right=mid-1;反之,left=mid+1
还有需要注意判断当前元素是否时峰值的条件(get(mid-1)<get(mid) and get(mid)>get(mid+1))
考虑到[mid-1,mid+1]属于[-1,n],所以定义一个def get(idx):来取数组中的数,if idx==-1 or idx==n: return float(“-inf”)

    class Solution(object):
	    def findPeakElement(self, nums):
	        """
	        :type nums: List[int]
	        :rtype: int
	        """
	        n=len(nums)
	        def get(idx):
	            if idx==-1 or idx==n:
	                return float("-inf")
	            else:
	                return nums[idx]
	        # left<=right,等于right的情况
	        left,right,ans=0,n-1,-1
	        while left<=right: 
	            mid=(left+right)//2
	            if (get(mid-1)<get(mid) and get(mid)>get(mid+1)):
	                return mid
	            if get(mid)<get(mid+1):
	                left=mid+1
	            else:
	                right=mid-1

例三:33. 搜索旋转排序数组

33. 搜索旋转排序数组

    二分法,虽然旋转后的数组本身不是增序,但是依然有增序的部分,可以通过mid和left\right的对比找到这些部分
    如果target在增序的部分,继续二分;如果不是,那把left=mid+1或right=mid-1,就是整个问题的递归

以nums = [4,5,6,7,0,1,2], target = 0为例,mid第一轮指向7,先判断7不是target,且>=nums[left],此时判断target=0即小于nums[mid]也小于nums[left],所以把left=mid+1,缩小搜索的范围;若target是5,target<nums[mid] and target>=nums[left]为真,则right=mid-1来减小搜索的范围。

相比直接的二分查找,这题要讨论的条件更多一点

class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
  
        n=len(nums)
        left,right=0,n-1
        while left<=right:
            mid=(left+right)//2
            if nums[mid]==target:
                return mid
            if nums[mid]>=nums[left]:
                if target<nums[mid] and target>=nums[left]:
                    right=mid-1
                else:
                    left=mid+1
            if nums[mid]<=nums[right]:
                if target>nums[mid] and target<=nums[right]:
                    left=mid+1
                else:
                    right=mid-1

        return -1

二分法的实际应用题

例四:2187. 完成旅途的最少时间

给你一个数组 time ,其中 time[i] 表示第 i 辆公交车完成 一趟旅途 所需要花费的时间。

每辆公交车可以 连续 完成多趟旅途,也就是说,一辆公交车当前旅途完成后,可以 立马开始 下一趟旅途。每辆公交车 独立 运行,也就是说可以同时有多辆公交车在运行且互不影响。

给你一个整数 totalTrips ,表示所有公交车 总共 需要完成的旅途数目。请你返回完成 至少 totalTrips 趟旅途需要花费的 最少 时间。

输入:time = [1,2,3], totalTrips = 5
输出:3
解释:

  • 时刻 t = 1 ,每辆公交车完成的旅途数分别为 [1,0,0] 。
    已完成的总旅途数为 1 + 0 + 0 = 1 。
  • 时刻 t = 2 ,每辆公交车完成的旅途数分别为 [2,1,0] 。
    已完成的总旅途数为 2 + 1 + 0 = 3 。
  • 时刻 t = 3 ,每辆公交车完成的旅途数分别为 [3,1,1] 。
    已完成的总旅途数为 3 + 1 + 1 = 5 。
    所以总共完成至少 5 趟旅途的最少时间为 3 。
class Solution(object):
    def minimumTime(self, time, totalTrips):
        """
        :type time: List[int]
        :type totalTrips: int
        :rtype: int
        """

        #至少 totalTrips 趟旅途需要花费的最少时间。
        #最多花费min(time)*totaltrip的时间
        #l=0 r=min(time)*totaltrip
        #check(time)#测试T时刻是否可以完成旅途

        def check(T):
            res=0
            for t in time:
                res+=T//t
            return res>=totalTrips

        l,r=1,min(time)*totalTrips #为了防止除数为0,且时刻最少也是1
        ans=0
        while l<=r: #想想为什么是<=,例子 time = [2], totalTrips = 1
            mid=(l+r)//2
            if check(mid):
                ans=mid
                r=mid-1
            else:
                l=mid+1

        return ans

例五:2226. 每个小孩最多能分到多少糖果

给你一个 下标从 0 开始 的整数数组 candies 。数组中的每个元素表示大小为 candies[i] 的一堆糖果。你可以将每堆糖果分成任意数量的 子堆 ,但 无法 再将两堆合并到一起。

另给你一个整数 k 。你需要将这些糖果分配给 k 个小孩,使每个小孩分到 相同 数量的糖果。每个小孩可以拿走 至多一堆 糖果,有些糖果可能会不被分配。

返回每个小孩可以拿走的 最大糖果数目 。

输入:candies = [5,8,6], k = 3
输出:5
解释:可以将 candies[1] 分成大小分别为 5 和 3 的两堆,然后把 candies[2] 分成大小分别为 5 和 1 的两堆。现在就有五堆大小分别为 5、5、3、5 和 1 的糖果。可以把 3 堆大小为 5 的糖果分给 3 个小孩。可以证明无法让每个小孩得到超过 5 颗糖果。

输入:candies = [2,5], k = 11
输出:0
解释:总共有 11 个小孩,但只有 7 颗糖果,但如果要分配糖果的话,必须保证每个小孩至少能得到 1 颗糖果。因此,最后每个小孩都没有得到糖果,答案是 0 。

class Solution(object):
    def maximumCandies(self, candies, k):
        """
        :type candies: List[int]
        :type k: int
        :rtype: int
        """
        
        #相同数量的糖果,只能分不能和

        #当分配i个糖果时,是否符合要求
        def check(i):
            res=0
            for c in candies:
                res+=c//i
            return res>=k

        l,r=1,max(candies) #不设为0,max(candies)的原因是防止mid=(0+1)//2=0,导致check函数的除数为0
        ans=0

        while l<=r:
            mid=(l+r)//2
            if check(mid):
                ans=mid
                l=mid+1
            else:
                r=mid-1

        return ans
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值