整数型二分查找详解

整数型二分查找详解

整数型二分一共有两个,分别适用于不同的情况。

版本1:你要找的数字在右边

int binary_search(int l,int r)
{
    while(l < r)
    {
        int mid = l + r + 1 >> 1;
        if(check(mid))
        {
            l = mid;
        }
        else
        {
            r = mid - 1;
        }
    }

    return l;
}

举一个例子:数组:1 2 2 2 3 4 六个数 查找3(3 是偏靠右边的数字 上帝视角)

那么mid = (0 + n - 1 + 1) / 2 = (0 + 6) / 2 = 3 a[3] = 2

2 < 3 那么很显然,需要将l = mid 意思就是:这个数组是有序的,mid位子上的数字小于你要查找的数字,那么下一轮查找就要让左边界l = mid 反正mid左边的那些数字都小于3,这是缩小范围的过程!,

然后l = mid = 3, mid = (3 + 5 )/ 2 = 4 a[mid] = 3 找到。

同时你也可以认为模板二是从右往左查找第一个小于等于x。

版本2:你要找的数字在左边

int binary_search(int l,int r)
{
    while(l < r>
    {
        int mid = l + r >> 1;
        if(check(mid))
        {
            r = mid;
        }
        else
        {
            l = mid + 1;
        }
    })
}

举一个例子:数组 1 2 3 3 4 5 查找2
那么mid = (0 + 5) / 2 = 2 a[mid] = a[2] = 3

a[mid] > 2 对吧,所以,由于这个数组是有序的,你要查找的数字一定在左边,所以你为了缩小范围,就需要让r = mid = 2 下一步就找到数值2

同时你也可以认为模板二是从左往右查找第一个大于等于x

问题一、那么到底该如何选取模板?

我们只需要使用mid位上的数值与需要查找的数字进行比较即可,如果x > a[mid],说明你要查找的数字在右边,那么就是用模板一,如果你要查找的数字在左边(x < a[mid]),那么你就使用模板2

问题二、为什么模板一需要使用mid = l + r + 1 >> 2 但是模板二没有?

举一个例子,如果数组只有两个数3 5,l与r只差一,l = 0 r = 1,查找数字5

那么 假设我要查找右边的数字1 那么不加1,mid = l + r >> 2 mid = 0

a[mid] = 3 < 5 说明 你要查找的数字在右边, 对吧, 然后,你就需要更新l = mid = 0,

l = 0, 那么这不等于没变化吗? 所以我们需要加上1,才可以

那么,问题来了,模板二就没有这种情况吗?

同样的例子,数组只有两个元素3 5,那么你要查找数字3

mid = (0 + 1) / 2 = 0,a[0] = 3 查找成功。

问题三、如果找不到数字是啥情况?

对于模板二,数组 1 1 3 3 4 5 查找数字 2

那么第一步,mid = (0 + 5) / 2 = 2 a[2] = 3 > 2 那么你要找的数字在左边,所以你也不需要写成mid = l + r + 1 >> 2,那么 r = mid (缩小范围),之后,mid = (0 + 2) / 2 = 1 a[1] = 1,那么a[mid] < 2,则 l = mid + 1 = 2,这就造成了r = l = 2, a[r] = a[l] = 3 > 2,那么它将永远也找不到数字2,可以看到这也应证了上面的一句话,模板二是从左往右找第一个大于或者等于x的数字,只不过,你可以理解为:找得到话:是正好等于x,找不到的话:是大于x

看一道例题:789.数的范围

在这里插入图片描述

AC代码:

#include<iostream>
using namespace std;

const int N = 1e5 + 10;

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)
        {
            //说明没找到,并且q[l] 是第一个大于x的数字
            cout<<"-1 -1"<<endl;
        }
        else
        {
            cout<<l<<" ";  // 查找到数字 
            // 但是使用模板二是从左往右查找的第一个大于或者等于x的数字 偏向于x
            // 这只从左边找到一个数字,但是 这道题要查找的数字是多个的
            // 这就意味着我还要从右往左在查找一次数字  这样就找到一个数字的下标范围
            
            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;  // 这里输出的l 是从右往左找第一个小于或者等于x的数字

        }
    }

    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少写代码少看论文多多睡觉

求打赏,求关注,求点赞

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值