基础算法4 - 二分搜索

247 阅读1分钟

binary search 定义

想要使用“二分搜索”,这“一堆数”必须有以下特征:

  • 存在数组中
  • 有序排列(无序的话,二分法用来当猜答案的方法

模版

image.png

二分法的传统应用

PS:模版二、三最好

278. 第一个错误的版本(Easy)

image.png

Solu:

运用「模版三」:mid = left是最后一个good version;right = left + 1是第一个bad version

Code:

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


4. 寻找两个正序数组的中位数(Hard)

image.png

Solu:

image.png

image.png

  • mid1 = nums1分割后右半部分的起始index
    • 一分为二,只和切口处的4个elements有关
    • 因为#分割线左半边元素的总数 = (len(nums1)+len(nums2)) // 2是固定的,所以可以顺势计算出nums2的分割点mid2
  • 一旦满足分割线左半部分的所有元素 < 分割线右半部分的所有元素,则找到了“中位数”
    • 此时根据总长度的奇偶性判断median具体怎么算

Code:

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        if len(nums1) > len(nums2):
            nums1, nums2 = nums2, nums1
        l, r, total = 0, len(nums1), len(nums1) + len(nums2)
        while l <= r:
            m1 = (l + r) // 2
            m2 = (total + 1) // 2 - m1
            left1 = -float('inf') if m1 == 0 else nums1[m1 - 1]
            right1 = float('inf') if m1 == len(nums1) else nums1[m1]
            left2 = -float('inf') if m2 == 0 else nums2[m2 - 1]
            right2 = float('inf') if m2 == len(nums2) else nums2[m2]
            if max(left1, left2) <= min(right1, right2):
                if total % 2 == 0:  # even length
                    return float((max(left1, left2) + min(right1, right2)) / 2)
                else:  # odd length
                    return float(max(left1, left2))
            elif right1 < left2:  # nums1's left partition is too small
                l = m1 + 1
            else:  # nums2's left partition is too small
                r = m1 - 1
        return -1


二分法去猜最终答案

❤️ 410. 分割数组的最大值

image.png

Solu:

  • max(nums) ≤ 子数组的最大值 ≤ sum(nums)必定成立
  • 先猜一个最终结果mid值,然后遍历数组划分,使每个子数组和都最接近mid贪心地逼近mid),这样得到的满足sum(subArray) <= mid的子数组数一定最少;
    • 如果即使这样,仍旧#子数组数量 > m - 1个,那么明显说明mid猜小了,因此 lo = mid + 1;
    • 而如果得到的#子数组数量 ≤ m - 1个,那么可以尝试更小的mid,因此 hi = mid - 1;

当问题除了暴力解没有其他思路的时候,用二分法去“猜”答案也是一种解法

Code:

class Solution:
    def splitArray(self, nums: List[int], m: int) -> int:
        def valid(subArraySum):
            curSum, count = 0, 0
            for num in nums:
                curSum += num
                if curSum > subArraySum:
                    count += 1
                    curSum = num
                    if count >= m:
                        return False
            return True
        
        def binary(low, high):
            while low <= high:
                mid = (low + high) // 2
                if valid(mid):  # 尽量往小的答案猜
                    high = mid - 1
                else:
                    low = mid + 1
            return low
        
        return binary(max(nums), sum(nums))

同思路类似题目还有:

  • 1552. Magnetic Force Between Two Balls
    1. Minimum Number of Days to Make m Bouquets
    1. Find the Smallest Divisor Given a Threshold
    1. Maximum Side Length of a Square with Sum Less than or Equal to Threshold


Reference: