leetcode刷题记录: 二分查找

参考:labuladong的算法小抄https://labuladong.online/algo/essential-technique/binary-search-framework/

1. 二分搜索框架

def binarySearch(nums, target):
    left, right = 0, len(nums)-1
    while left <= right:
        mid = left + (right-left)//2 # 这里和(left+right)//2的效果一样,但可以防止溢出
        if (nums[mid] == target):
            ...
        elif (nums[mid] < target):
            left = ...
        elif (nums[mid] > target):
            right = ...
    return ...  

二分查找的技巧一:不要有else,把所有的可能性都用else if列举出来。

2. 基本的二分搜索:寻找一个数

寻找一个数,如果存在返回其索引,否则返回-1

def binarySearch(nums, target):
    left, right = 0, len(nums)-1
    while left <= right:
        mid = left + (right-left)//2 # 这里和(left+right)//2的效果一样,但可以防止溢出
        if (nums[mid] == target):
            return mid
        elif (nums[mid] < target):
            left = mid + 1
        elif (nums[mid] > target):
            right = mid - 1
    return ...  

几个要点

  • 循环条件是left <= right,不是left < right.
    其实两个都可以,看写法。这种写法下两端都是闭区间,right初始化为len(nums)-1而不是len(nums)
  • 为什么elif是left = mid + 1而不是mid?(right同理)
    因为我们搜索的是闭区间。已知nums[mid] != target, 下一步搜索就要把mid这个位置排除
  • 该方式的缺点
    当有重复元素的时候,比如[1,2,2,2,3],target = 2,我想得到左侧边界的索引1,或者右侧边界的索引3,就无法做到

3. 寻找左侧边界的二分搜索

def binarySearch(nums, target):
    left, right = 0, len(target) #注意
    while (left < right):
        mid = left + (left+right)//2
        if (nums[mid] == nums[target]):
            right = mid #注意
        elif (nums[mid] < nums[target]):
            left = mid + 1
        elif (nums[mid] > nums[target]):
            right = mid #注意
    if (left < 0 or left > len(nums)-1): # left<0可以不要,但这里都写上,形式更统一,也更方便记忆
        return -1
    # 注意返回时判断是否相等
    if (nums[left] == target):
        return left
    else:
        return -1

  • 这个方法是左闭右开区间,所以right初始化为len(nums), 二分搜索是right = mid
  • 为什么能搜索到左侧边界:找到 target 时不会立即返回,而是缩小「搜索区间」的上界 right
  • 为什么返回left而不是right?
    都是一样的,因为while的终止条件是left == right. 可以这么记:因为是搜索左侧边界所以返回left。

下面尝试统一写法,写成左右都闭区间的形式

def binarySearch(nums, target):
    left, right = 0, len(nums) - 1
    while left <= right:
        mid = left + (right-left)//2
        if (nums[mid] == target):
            right = mid - 1
        elif (nums[mid] < target):
            left = mid + 1
        elif (nums[mid] > target):
            right = mid - 1
    if (left < 0 or left > len(nums)-1): # left<0可以不要,但这里都写上,形式更统一,也更方便记忆
        return -1
    if (nums[left] == target):
        return left
    else:
        return -1

3. 寻找右侧边界的二分搜索

直接套用公式。因为是右侧边界最终返回right即可

def binarySearch(nums, target):
    left, right = 0, len(nums)-1
    while left <= right:
        mid = left + (right-left)//2
        if nums[mid] == target:
            left = mid + 1
        elif nums[mid] < target:            
            left = mid + 1
        else:
            right = mid - 1
    if right < 0 or right > len(nums)-1:
        return -1
    if nums[right] == target:
        return right
    else:
        return -1

leetcode实战

leetcode 34 在排序数组中查找元素的第一个和最后一个位置

这道题是需要我们寻找target的左侧边界和右侧边界。最直观的解法,写两个函数lowerBound和upperBounder, 分别计算。

class Solution(object):
    def searchRange(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        start = self.lowerBound(nums, target)
        end = self.upperBound(nums, target)
        return [start, end]
    def lowerBound(self, nums, target):
        left, right = 0, len(nums)-1
        while (left <= right):
            mid = left + (right-left)//2
            if (nums[mid] == target):
                right = mid - 1
            elif (nums[mid] < target):
                left = mid + 1
            elif (nums[mid] > target):
                right = mid - 1
        if left < 0 or left > len(nums) - 1:
            return -1
        if nums[left] == target:
            return left
        else:
            return -1

    def upperBound(self, nums, target):
        left, right = 0, len(nums)-1
        while (left <= right):
            mid = left + (right-left)//2
            if (nums[mid] == target):
                left = mid + 1
            elif (nums[mid] < target):
                left = mid + 1
            elif (nums[mid] > target):
                right = mid - 1
        if right < 0 or right > len(nums) - 1:
            return -1
        if nums[right] == target:
            return right
        else:
            return -1

但是这种写法比较冗余,可以只写一个lowerBound. 返回参数只返回left,不判断其他情况。此时upperBound的含义就变了,它的作用是:

  • 如果target在nums中存在,则返回target下界的index;
  • 如果target在nums中不存在,返回的是第一个大于target的数字的index;
    (如果target比nums中所有元素都大,则返回len(nums))

在主函数中判断,如果下界存在,则上界也必存在,再调用upperBound(nums, target+1)计算出target+1的下界index,再-1就是target的上界. 完整代码如下:

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

看了题解区另一位老哥的解法,简单粗暴

class Solution(object):
    def searchRange(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        lo, hi = 0, len(nums)-1
        res = [-1, -1]
        # 找第一个等于target的位置
        while lo <= hi:
            mid = lo + (hi-lo)//2
            if nums[mid] < target:
                lo = mid + 1
            elif nums[mid] > target:
                hi = mid - 1
            else:                
                hi = mid - 1
                res[0] = mid # 重点
        lo, hi = 0, len(nums)-1
        // 找最后一个等于target的位置
        while lo <= hi:
            mid = lo + (hi-lo)//2
            if nums[mid] < target:
                lo = mid + 1
            elif nums[mid] > target:
                hi = mid - 1
            else:
                lo = mid + 1
                res[1] = mid # 重点
        return res

leetcode704 二分查找

https://leetcode.cn/problems/binary-search/
没啥好说的

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

最长递增子序列

力扣300题:https://leetcode.cn/problems/longest-increasing-subsequence/description/
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
这道题的常见解法是动态规划,O(n^2); 用贪心+二分查找的方式,可以达到时间复杂度O(nlogn).
核心思路参考动态规划的文章,这里贴一下代码:

class Solution(object):
    def lengthOfLIS(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        def upperBound(arr, target):  
            lo, hi = 0, len(arr)-1
            while lo <= hi:
                mid = lo + (hi-lo)//2
                if arr[mid] < target:
                    lo = mid + 1
                elif arr[mid] > target:
                    hi = mid - 1
                elif arr[mid] == target:
                    hi = mid - 1            
            return lo
        d = []
        for n in nums:
            if not d or n > d[-1]:
                d.append(n)
            else:
                idx = upperBound(d, n)
                # 一般来说,当n>d[-1]时,idx = len(d),会越界;但上面if已经判断了n>d[-1],这里不会出现越界的情况
                d[idx] = n
        return len(d)

二分查找的应用:吃香蕉

class Solution(object):
    def minEatingSpeed(self, piles, h):
        """
        :type piles: List[int]
        :type h: int
        :rtype: int
        """
        def helper(piles, k):
            hour = 0
            for i in range(len(piles)):
                hour += piles[i]//k
                if piles[i]%k > 0:
                    hour += 1
            return hour
		# 注意lo和hi的起始位置            
        lo, hi = 1, max(piles)
        res = 0
        # 注意不用新建一个数组,否则会内存超限
        while lo <= hi:
            mid = lo + (hi-lo)//2
            if helper(piles, mid) < h:
                hi = mid - 1
            elif helper(piles, mid) > h:
                lo = mid + 1
            elif helper(piles, mid) == h:
                hi = mid - 1
        res = lo
        return res
  • 19
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值