二分法

这里我们对二分法的初始值,循环条件,条件语句进行讨论。

初始值

对于左右两个端点的初始值,在一个长度为 nn 的数组上,左经常用到的有两种情况:

  • 左闭右闭,即[0,n1][0, n-1]
  • 左闭右开,即[0,n)[0, n)

上述两种初始化对应的中间值计算方式均为

m=(rl)2+l.\begin{aligned} m = \lfloor\frac{(r - l)}{2}\rfloor + l. \end{aligned}

循环条件

对于循环条件我们经常见到的也有两种情况:

  • lrl \leq r,对应着l>rl > r退出循环
  • l<rl < r,对应着l=rl =r退出循环

上面两种循环条件各自对应着以下两种更新方式

  • l=m+1l=m+1, r=m1r = m -1
  • l=m+1l=m+1, r=mr = m

也不难理解,因为rr为开区间,在第二种更新中并没有取m1m-1。(个人更喜欢第一种循环和更新。)

第一种更新需要注意的特殊场景是,返回的ll或者rr可能是超出范围的。例如当target大于所有元素时,判断条件是l=m+1l=m+1是,最终返回的是l=nl=n

条件语句

在各种类似“第一个大于k的坐标”,“小于等于k的最小坐标”等等题目中,if-else判断条件起着重要的作用,例如下面的代码中返回的 ll 是大于 target的最小值的坐标。

def f(nums, l, r)
    while l <= r:
        mid = (r - l) // 2 + l
        if nums[mid] <= target:
            l = mid + 1  # 确保更新后左侧的区域,( ,l), 一定小于等于target
        else:
            r = mid -1  # 确保更新后右侧的区域, (r, ), 一定是大于target
    return l

例题

对二分法本质更深的理解

153. 寻找旋转排序数组中的最小值

162. 寻找峰值

二分法的本质是通过判断中间值是否满足一定条件,来舍弃掉一半的搜索范围,实现 O(logn)O(\log n) 的复杂度。

更广泛的target函数

668. 乘法表中第k小的数

参考资料