leetcode刷题记录:二分搜索的应用

https://labuladong.online/algo/frequency-interview/binary-search-in-action/ 和https://labuladong.online/algo/problem-set/binary-search/

1. 二分搜索的框架

# 函数 f 是关于自变量 x 的单调函数
def f(x: int) -> int:
    # ...

# 主函数,在 f(x) == target 的约束下求 x 的最值
def solution(nums: List[int], target: int) -> int:
    if len(nums) == 0:
        return -1
    # 问自己:自变量 x 的最小值是多少?
    left = ...
    # 问自己:自变量 x 的最大值是多少?
    right = ... + 1

    while left < right:
        mid = left + (right - left) // 2
        if f(mid) == target:
            # 问自己:题目是求左边界还是右边界?
            # ...
        elif f(mid) < target:
            # 问自己:怎么让 f(x) 大一点?
            # ...
        elif f(mid) > target:
            # 问自己:怎么让 f(x) 小一点?
            # ...
    return left

2. 吃香蕉 & 运货物问题

2.1 coco吃香蕉

https://leetcode.cn/problems/koko-eating-bananas/
f(k): 吃香蕉的速度为k,返回多长时间能吃完。寻找f(k) == h的左侧边界
注意f(k)对k是递减的,二分搜索的时候lo hi的增减要相应变化

class Solution(object):
    def minEatingSpeed(self, piles, h):
        """
        :type piles: List[int]
        :type h: int
        :rtype: int
        """
        def time(piles, k):
            res = 0
            for p in piles:
                res += p//k
                if p%k != 0:
                    res += 1
            return res
        lo, hi = 1, max(piles)
        while lo <= hi:
            mid = lo + (hi-lo)//2
            if time(piles, mid) > h:
                lo = mid + 1
            else: 
                hi = mid - 1
        return lo

2.2 运送货物问题

https://leetcode.cn/problems/capacity-to-ship-packages-within-d-days/
和吃香蕉问题逻辑基本一样,区别在于一艘船可以装多个货物,time函数要稍作处理。相当于吃完这堆香蕉后这个小时内还会继续吃其他的香蕉。

class Solution(object):
    def shipWithinDays(self, weights, days):
        """
        :type weights: List[int]
        :type days: int
        :rtype: int
        """
        def time(piles, k):
            res = 1
            temp = k
            for p in piles:
                if temp >= p:
                    temp -= p
                else:
                    temp = k - p
                    res += 1
            return res
        lo, hi = max(weights), sum(weights)
        while lo <= hi:
            mid = lo + (hi-lo)//2
            if time(weights, mid) > days:
                lo = mid + 1
            else: 
                hi = mid - 1
        return lo

2.3 分割数组的最大值

这道题,看起来比较绕,其实是跟2一模一样的,只是换了个皮。

  • 原题:给定一个非负整数数组 nums 和一个整数 k ,你需要将这个数组分成 k 个非空的连续子数组。设计一个算法使得这 k 个子数组各自和的最大值最小。
  • 改造:你只有一艘货船,现在有若干货物,每个货物的重量是 nums[i],现在你需要在 m 天内将这些货物运走,请问你的货船的最小载重是多少?
class Solution(object):
    def splitArray(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: int
        """
        def helper(weights, x):
            d = 1
            temp = x
            for w in weights:
                if temp >= w:
                    temp -= w
                else:
                    temp = x - w
                    d += 1
            return d
        lo, hi = max(nums), sum(nums)

        while lo <= hi:
            mid = lo + (hi-lo)//2
            if helper(nums, mid) > k:
                lo = mid + 1
            else:
                hi = mid - 1  
        return lo      

3. 二分搜索应用:搜索二维矩阵

3.1 搜索二维矩阵1

https://leetcode.cn/problems/search-a-2d-matrix/description/
思路1:二维转一维,把m x n矩阵转化为长度为m x n的一维数组,用一次二分搜索。

class Solution(object):
    def searchMatrix(self, matrix, target):
        """
        :type matrix: List[List[int]]
        :type target: int
        :rtype: bool
        """
        def get(matrix, i):
            row = i // len(matrix[0])
            col = i % len(matrix[0])            
            print(i, row, col)
            return matrix[row][col]
        lo, hi = 0, len(matrix) * len(matrix[0]) - 1
        while lo <= hi:
            mid = lo + (hi-lo)//2
            if get(matrix, mid) == target:
                return True
            elif get(matrix, mid) > target:
                hi = mid - 1
            else:
                lo = mid + 1
        return False

思路2:对列和行分别做两次二分搜索

class Solution(object):
    def searchMatrix(self, matrix, target):
        """
        :type matrix: List[List[int]]
        :type target: int
        :rtype: bool
        """
        if not matrix:
            return False
        m, n = len(matrix), len(matrix[0])
        
        lo, hi = 0, m-1
        while lo <= hi:
            mid = lo + (hi-lo)//2
            if matrix[mid][0] == target:
                return True
            elif matrix[mid][0] > target:
                hi = mid - 1
            else:
                lo = mid + 1        
        if hi < 0:
            return False
        else:
            row = hi
        lo, hi = 0, n-1
        while lo <= hi:
            mid = lo + (hi - lo)//2
            if matrix[row][mid] == target:
                return True
            elif matrix[row][mid] > target:
                hi = mid - 1
            else:
                lo = mid + 1
        return False
        

3.2 搜索二维矩阵2

https://leetcode.cn/problems/search-a-2d-matrix-ii/description/
Z字形查找,从右上角开始搜索,如果> target就往左,否则往下。
时间复杂度O(m+n)

class Solution(object):
    def searchMatrix(self, matrix, target):
        """
        :type matrix: List[List[int]]
        :type target: int
        :rtype: bool
        """
        m, n = 0, len(matrix[0]) - 1
        while m < len(matrix) and n >= 0:
            if matrix[m][n] > target:
                n -= 1
            elif matrix[m][n] < target:
                m += 1
            else:
                return True
        return False

4. 找到k个最接近的元素

https://leetcode.cn/problems/find-k-closest-elements/description/
先搜索左侧边界算法找出leftbound;
再left = lo-1, right = lo
然后向外扩散,双指针求解

class Solution(object):
    def findClosestElements(self, arr, k, x):
        """
        :type arr: List[int]
        :type k: int
        :type x: int
        :rtype: List[int]
        """
        lo, hi = 0, len(arr)-1
        while lo <= hi:
            mid = lo + (hi-lo)//2
            if arr[mid] < x:
                lo = mid + 1
            else:
                hi = mid - 1
        res = []
        left, right = lo - 1, lo
        while right - left - 1 < k:
            if left < 0:
                res.append(arr[right])
                right += 1
            elif right > len(arr)-1:
                res.insert(0, arr[left])
                left -= 1
            elif x - arr[left] > arr[right] - x:
                res.append(arr[right])
                right += 1
            else:
                res.insert(0, arr[left])
                left -= 1
        return res

5. 峰值问题

5.1 寻找峰值

https://leetcode.cn/problems/find-peak-element/
循环条件:lo < hi,而不是lo <= hi
判断条件: nums[mid] > nums[mid+1]

class Solution(object):
    def findPeakElement(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """ 
        lo, hi = 0, len(nums)-1       
        while lo < hi:
            mid = lo + (hi-lo)//2
            if nums[mid] > nums[mid+1]:
                hi = mid
            else:
                lo = mid + 1
        return lo

5.2 山脉数组的峰顶索引

https://leetcode.cn/problems/peak-index-in-a-mountain-array/description/
和5.1基本一致

class Solution(object):
    def peakIndexInMountainArray(self, arr):
        """
        :type arr: List[int]
        :rtype: int
        """
        lo, hi = 0, len(arr)-1
        while lo < hi:
            mid = lo + (hi-lo)//2
            if arr[mid] > arr[mid+1]:
                hi = mid
            else: 
                lo = mid + 1
        return lo

5.3 0到n-1中缺失的数字

leetcode:点名
https://leetcode.cn/problems/que-shi-de-shu-zi-lcof/
注意点:判断条件records[mid] > mid

class Solution(object):
    def takeAttendance(self, records):
        """
        :type records: List[int]
        :rtype: int
        """
        lo, hi = 0, len(records)-1
        while lo <= hi:
            mid = lo + (hi-lo)//2
            if records[mid] > mid:
                hi = mid - 1
            else:
                lo = mid + 1        
        return lo

6 搜索旋转排序数组

6.1 元素不可重复

https://leetcode.cn/problems/search-in-rotated-sorted-array/
先判断nums[mid]在左边还是右边,再判断target和nums[mid]的大小关系

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

6.2 元素可重复

https://leetcode.cn/problems/search-in-rotated-sorted-array-ii/
思路类似,也是先判断nums[mid]是在左边还是右边。但因为元素可重复,当nums[mid] == nums[lo] == nums[hi]的时候无法判断。因此对lo和hi区间进行缩减,不要出现相等的情况。

class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: bool
        """
        if not nums:
            return -1
        lo, hi = 0, len(nums)-1
        while lo <= hi:
            while lo < hi and nums[lo] == nums[lo+1]:
                lo += 1
            while lo < hi and nums[hi] == nums[hi-1]:
                hi -= 1
            mid = lo + (hi-lo)//2
            if nums[mid] == target:
                return True            
            if nums[mid] >= nums[lo]:
                if target >= nums[lo] and target < nums[mid] :
                    hi = mid - 1
                else:
                    lo = mid + 1
            else:
                if target > nums[mid] and target <= nums[hi]:
                    lo = mid + 1
                else:
                    hi = mid - 1
        return False

评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值