二分搜索

二分搜索类型

二分的本质不是单调性,而是二分性。意思就是,通过判断中点(可能要结合左右边界)的性质,我可以确定答案在左半段还是右半段。

因为这样的性质,二分有传说中的二分模版和各种边界条件,个人也有这里我觉得可以分两种情况讨论:

  1. 二分存在性搜索:在whlie循环中返回答案,循环中l<=r

  2. 二分最值搜索:在while循环后返回答案,循环中l<r

  3. 边界搜索:在while循环后返回答案,循环中l<=r

看模版中的注释来看细节。在判断区间时,总是用中点m来缩小区间至左边或右边。

参考模版:

"""
情况一:二分存在性搜索 - 在whlie循环中返回答案
      寻找target

循环:用l<=r 为条件开始搜索,这样如果中点m每次没有找到,
     就可以直接避开m缩小返回。
判断:如果中点符合条件,直接返回。
     如果不在[l,r]左区间则缩小至右区间搜索l=m+1
     否则r=m-1在左区间搜索
退出:如果l和r完美错过,那么没找到
"""

l, r = 0, len(nums)-1
while l<=r:
    m = (+r)>>1
    if nums[m]==n: # 找到了
        return m
    if {...}:      # 判断:如果不在[l, m]闭区间
        l = m+1
    else:
        r = m-1
return -1          # 没找到

情况一:存在性查找例题

  1. 二分查找(Binary)

def search(nums, target):
    l, r = 0, len(nums)-1
    while l<=r:
        m = (l+r) >> 1
        if nums[m]==target:
            return m
        if target>nums[m]:
            l = m+1
        else:
            r = m-1
    return -1

情况二:最值查找例题

def findPeakElement(nums):
    l, r = 0, len(nums)-1
    while l<r:
        m = l+(r-l) // 2
        if nums[m+1]>nums[m]:
            l = m+1
        else:
            r = m
    return l

情况三:边界搜索

极小极大化:本质就是用二分来找找到第一个/最后一个可行解,因为可行解都在答案的一侧。

把可能答案从小到大排列,可以发现可行性的排列大概是✖✖✖✖✔✔✔。我们极小极大化的答案就是第一个✔。而验证答案,即判断是✖还是✔这个过程,很多时候是用贪心来验证的,如果假设答案不可行就返回错误,可行,则判断是可能解再在左区间搜索。

  1. 分享巧克力(chocolate)

  2. H 指数 II (h-index)

left, right = 1, n
while left <= right:
    mid = (left + right) >> 1
    if isBadVersion(mid):
        right = mid - 1
    else:
        left = mid + 1
return left

bisect库函数

Python的bisect库让人挺偷懒的,一个具体的例子,在排序数组中查找元素的第一个和最后一个位置,相对对应着bisect的这两个方法:

# 元素target第一个插入点
# 如果在数组的末尾(所有数字都比target小),那么元素不存在
l = bisect.bisect_left(nums, target)

# 元素target的最后插入点
# 如果在数组的开头(所有数字都比target大),那么元素不存在
r = bisect.bisect_right(nums, target)

为了不偷懒,我们分别把这两个方法实现一下。

def bisect_left(nums, target):
    l, r = 0, len(nums)-1
    while l<=r:
        m = (l+r) >> 1
        if nums[m]>=target:
            r = m-1
        else:
            l = m+1
    return l

def bisect_right(nums, target):
    l, r = 0, len(nums)-1
    while l<=r:
        m = (l+r) >> 1
        if target>=nums[m]:
            l = m+1
        else:
            r = m-1
    # 因为bisect_right找的是插入点而非存在点
    # 所以如果target存在,返回它的下一个索引
    return l

def searchRange(nums, target):
    l = bisect.bisect_left(nums, target)
    r = bisect.bisect_right(nums, target)

    n = len(nums)
    l = l if l!=n and nums[l]==target else -1
    r = r-1 if r and nums[r-1]==target else -1
    return [l, r]

Last updated

Was this helpful?