【算法模版】——二分查找与二分答案

834 阅读3分钟

二分查找与二分答案

二分查找

  • 对于一个有序序列,通过二分折半查找,
  • 可以实现以 O(logn) 的复杂度找到目标值的位置或目标值的附属值 (在离散化操作中,查询映射后下标的操作)

二分答案

  • 对于已经给定答案区间,要求 “最小的最大”or“最大的最小” 时,多用二分答案来快速查找答案。

复杂度分析

  • 对于长度为n的序列,进行一次查找,由于每次查找区间长度会缩短一半,
  • 因此一次成功的二分查找复杂度为O(logn)
  • 对于m次二分操作,则复杂度会变为 O(m·logn) ,同时,根据题目的 check函数 ,代码运行常数会受到一定影响(即 check函数 越复杂常数越大,不保证出题人会恶意卡常)

题目可能的关键词

  1. 二分查找
  • 谨记:二分查找是工具
  • 对于题目中需要查找下标,多个序列求一个集合(i,j,k)的对数等,查找时从暴力角度思考会有遍历全程的操作,多用二分查找(不过一般也可以用map或者unordered_map来解决)。
  1. 二分答案
  • 若答案数字范围在 0≤n≤1e6 题目情景有比较明确的单调性存在
  • 对于求满足题意的最大值,至少可以满足题意思的最小值

二分模版

整数二分模版

  1. 写好check函数是完成二分查找的关键 bool check(int x) { }

  2. 整数二分让人头疼的边界问题

  • 由于c++,java中除法默认是向下取整,
  • 因此,mid在确定时需要明确是否进行向上取整处理
  • 这个时候背一下固定的模版就会很有用

模版 bserach_l

  • 寻找第一个大于(等于)条件的最小值
int bserach_l(int l, int r, int x)
{
    while (l < r) {
        int mid = l + r >> 1;//不需要加,一因为求的是lmin
        if (check(mid))
            r = mid;
        else
            l = mid + 1;
    }
    return l;
}

模版 bserach_r

  • 寻找第一个小于(等于)条件的最大值
int bserach_r(int l, int r, int x)
{
    while (l < r) {
        int mid = l + r + 1 >> 1;//注意+1,rmax要向上取
        if (check(mid))
            l = mid;
        else
            r = mid - 1;
    }
    return l;
}

PS:本人秉持实用性原则,理解记忆同时进行。

  • 在更新 lr
  1. 以下情况不用 +1 r = mid,l = mid+1

  2. 以下情况要 +1 l = mid,r = mid-1

  • 边界问题的相关证明,可以写一写样例,大致的卡边界时,如果l = mid时,不加1,会导致在更新时一直是集合中有两个元素,从而导致死循环

模版题

AcWing 789. 数的范围

浮点数二分模版

  • 用的比较少 可以用来求根,简单来说就是二分迭代求根,收敛的速度不是很快 but,如果求根还是首推牛顿迭代,或者在用一下埃特金加速,在机器学习更常用

浮点数二分模版

bool check(double x) {} //同样的check函数
double bsearch_double(double l, double r)
{
    const double eps = 1e-8;
    //eps表示精度,一般比题目要求多2位就行了
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}

模版题

AcWing 790. 数的三次方根9827