二分查找初学心得总结

119 阅读3分钟

1 适用范围

在一个区间上,通过一个性质,可以将区间严格地区分为matched(匹配)和unmatched(不匹配)两部分(这是我自己定义的,方便下面叙述),现在的目的是寻找两部分的分界点(matched区间上靠近unmatched区间的端点)。

image.png

image.png (我自己用ps画的图,图丑请见谅 (/⊙ω⊙)/)

2 模板

模板一:适用于matched区间在左侧的情况。

int q[N];
int l = 0 , r = n - 1;
while( l < r )
{
    int mid = ( l + r + 1 ) >> 1;
    if( check(q[mid]) ) l = mid;
    else r = mid - 1;
}
returen l;//此时l==r

模板二:适用于matched区间在右侧的情况。

int q[N];
int l = 0 , r = n - 1;
while( l < r )
{
    int mid = ( l + r ) >> 1;
    if( check(q[mid]) ) l = mid;
    else l = mid + 1;
}
returen l;//此时l==r

3 边界问题

  • 为什么模板1是 mid = ( l + r + 1 ) >> 1 而模板二是 mid = ( l + r ) >> 1 ?

当二分的区间较大时,mid取哪一个均不影响二分,真正使二分结果产生偏差的是在最后一步。不难发现,此二分方法的最后一步的步长始终为1,即l和r相邻,因此可以分为两种情况,l在matched上和r在matched上,如图: image.png

现在我们的目的是寻找到matched区间在不同侧条件下,能同时满足 case1 和 case2 的l和r指针的移动方法。

模板一:matched区间在左侧

若当前mid在 check( q[mid] ) 后为ture,则移动左指针( l = mid ), 对应于 case1 , 移动后 l == r == mid == k 。考虑到c++是向下取整,不难发现,mid == ( k - 1 ) + ( k ) + 1 >> 1 == l + r + 1 >> 1;

若当前check(q[mid])为false时,移动右指针 r( r = mid -1 ), 对应于case2 , 移动后 r == l == mid -1 == k,即 mid == k + 1 == ( k ) + ( k + 1 ) + 1 >> 1 == l + r + 1 >> 1; 综上,不难发现mid的表达式应该为:

                                mid == l + r + 1 >> 1

模板二:matched区间在右侧

若当前mid在check( q[mid] )后为true,则右指针r向左移动( r == mid ),对应case2,移动后应该为: r == l == mid == k,不难发现 mid == ( k ) + ( k+1 ) >> 1 == l + r >> 1;

若当前mid在check( q(mid) )后为false,则左指针向右移动( l = mid +1 ),对应case1,移动后应该为 l == r == mid +1 == k,即 mid == k - 1 == ( k - 1 ) + ( k ) >> 1 == l + r >> 1; 综上,不难发现mid的表达式应该为:

                                mid == l + r >> 1

4 一般解题思路

graph TD
id1[设计check函数]
id2[识别模板]
id3[编写代码]
id1 --> id2 --> id3
  • 设计check()函数时要注意:最后返回端点处经check()后一定要为ture
  • 识别模板:根据check()函数的逻辑判断matched区间在哪一册,然后对应到模板。或者是思考check()为true后,指针如何变化,然后对应模板

5 例子:数的范围

  • 题目 image.png
  • 思路:首先二分寻找左端点,左端点满足的性质是大于等于x(x是询问的数),因此check()函数就可以直接写为 q[mid] >= x 。有人会问,为什么左端点同样满足小于等于x,为什么check()函数不能写为 q[mid] <= x 呢?因为左端点右侧如果也有与左端点值相等的数,那么该性质不能从左端点处将区间分开。因此, 前面第一步的分析思路有问题。事实上,我们寻找的不是端点处满足的性质,而是从端点处起一侧满足而另一侧不满足的性质。按照这种思路来看,右端点及右端点左侧满足的条件是 小于等于x, 那么check()函数可以写为 q[mid] <= x。解决了check()函数接下来就是识别模板类型了,这一步比较简单,就不多赘述了,下面附上 吸佳佳 代码 (^^)/。
  • 吸佳佳代码
#include <iostream>

using namespace std;

const int N = 100010;

int n, m;
int q[N];

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);

    while (m -- )
    {
        int x;
        scanf("%d", &x);

        int l = 0, r = n - 1;
        while (l < r)
        {
            int mid = l + r >> 1;
            if (q[mid] >= x) r = mid;
            else l = mid + 1;
        }

        if (q[l] != x) cout << "-1 -1" << endl;
        else
        {
            cout << l << ' ';

            int l = 0, r = n - 1;
            while (l < r)
            {
                int mid = l + r + 1 >> 1;
                if (q[mid] <= x) l = mid;
                else r = mid - 1;
            }

            cout << l << endl;
        }
    }

    return 0;
}