二分查找思想 + AcWing 789. 数的范围

二分查找的思想:

确定一个区间,使得我们要找的目标值一定在这个区间内出现
找一个性质,满足以下两点(整数二分和实数二分通用):

  • (1)性质具有 二段性(100%一定成立)
    • 二段性含义:在一段区间中,前面一段连续的部分满足这个性质,后面一段连续的部分不满足这个性质,两部分无缝衔接(满足和不满足是相对的),就如下图:
      在这里插入图片描述
  • (2)二分的答案 是 二段性的分界点。以整数二分为例子,有两种情况:“红色区间的右端点” 以及 “绿色区间的起点”,如下图:
    在这里插入图片描述

对于整数二分,二分答案有两种情况,对于不同的情况我们对应有着不同的做法。

接下来我们看看对于这两种情况如何进行整数二分。

第一类:

二分的答案ans红色区间的右端点

在这里插入图片描述

假设当前区间范围是[L, R],并确保答案一定在当前区间内,设当前的中点为mid

对于第一类我们可以将整个区间分成两段:[L, mid-1][mid, R]

  • if(mid是红色) 说明ans必然在[mid, R]中,有可能和mid重合(因为 midans 都是红色的
  • else if(mid是绿色) 说明ans必然在[L, mid-1]

上述对应到代码上,整数二分模板一如下:

while(l<r)
{
	int mid = (l+r+1)/2;//如果下方是l=mid,则应当补上+1
	if(mid是红色) l=mid;
	else r=mid-1;
}

第二类:二分的答案ans绿色区间的左端点

在这里插入图片描述

对于第二类我们可以将整个区间分成两段:[L, mid][mid+1, R]

  • if(mid是绿色) 说明ans必然在[L, mid]中,有可能和mid重合(因为 midans 都是绿色的
  • else if(mid是红色) 说明ans必然在[mid+1, R]中(ansmid严格右边,不可为mid

上述对应到代码上,整数二分模板二如下:

while(l<r)
{
	int mid = (l+r)/2;//如果下方是r=mid,不要+1
	if(mid是绿色) r=mid;
	else l=mid+1;
}

总结:

在这里插入图片描述

例题:AcWing 789. 数的范围

在这里插入图片描述
在这里插入图片描述

题意:

给定一个已经排好序的,且长度为n的升序数组,和q个询问,对于每个查询返回元素k的起始位置和终止位置

思路:

根据上面的总结,

第一步,我们先找到一个区间,使得答案一定在这个区间中,我们的区间确定为[0, n-1]

第二步,找一个判断条件,使得该条件具有二段性,并且答案一定是该二段性的分界点,不过这里的“答案”并不一定是这道题要求的答案,我们需要找一个目标值(边界),这个目标值不一定是这道题所要求的值。

不妨先找左端点,想一想左端点具有什么样的性质,即 用什么样的性质 使 左端点成为二段性的一个分界点。假设我们当前要找的值是x,那么左端点相当于在整个数组中大于等于x的第一个位置

因此,二段性可以这样设置,判断条件为:q[mid]>=x判断每个位置的值是否大于等于x),如果x存在的话,那么 最终要二分的答案x 位置就一定位于满足 q[mid]>=x的第一个位置

在这里插入图片描述
如上图,红色区间所有数都是不满足>=x的绿色区间所有数都是满足>=x的q[mid]>=x这个条件可以将答案x变成进行二分的一个分界点

找到左端点后,L==R,我们还要判断是否有解,即判断是否有q[L]==q[R],如果不成立,则说明无解,如成立,则说明LRx的左端点

找左端点的过程运用上面总结的整数二分模板二左边红色部分不满足>=x右边绿色部分满足>=x,寻找 绿色区间的左端点

代码如下:

	int l = 0, r = n-1;
    while(l<r)
    {
        int mid = l + r >> 1;
        if(a[mid]>=k) r = mid;//相当于:if(mid是绿色) 说明ans必然在[L, mid]中(r=mid),有可能和mid重合(因为 mid 和 ans 都是绿色的)
        else l = mid + 1;
    }
    //如果a[l]==x,则二分出来的 l or r 即为左端点的性质

之后找一下右端点(下图三角形所指位置),我们定一段区间:[上面找到的左边界, n-1],

在这里插入图片描述
我们定一个性质:q[mid]<=x,三角形及其左边的元素都是<=x的,三角形严格右边都是>x的,因此这个判断条件是具有二段性的,且答案(右端点)一定是该二段性的分界点。

因此寻找右端点的过程就对应的是上面总结的整数二分模板一(左边红色部分(对应上图三角形及其左边) 满足<=x右边绿色部分(对应上图三角形严格右边) 满足>=x,寻找 红色区间的右端点

代码如下:

	r = n - 1;
    while(l<r)
    {
        int mid = l + r + 1 >> 1;
        if(a[mid]<=k) l = mid;
        else r = mid - 1;
    }

时间复杂度:

O(nlogn)

总代码如下

//二分查找左右边界位置
#include<iostream>
using namespace std;
const int N = 1e5+10;
int n,q,k;
int arr[N];
bool check1(int mid)//寻找左边界判断函数
{
    if(arr[mid]>=k) return true;
    return false;
}
bool check2(int mid)//寻找右边界判断函数
{
    if(arr[mid]<=k) return true;
    return false;
}
int main()
{
    cin>>n>>q;
    for(int i=0;i<n;i++)
        scanf("%d", &arr[i]);
    while(q--)
    {
        cin>>k;
        int l=0,r=n-1;
        while(l<r)
        {
            int mid=l+r>>1;
            if(check1(mid)) r=mid;
            else l=mid+1;
        }
        if(arr[l]!=k) cout<<"-1 -1"<<endl;
        else
        {
            cout<<l<<' ';
            int l=0,r=n-1;
            while(l<r)
            {
                int mid=l+r+1>>1;
                if(check2(mid)) l=mid;//当更新方式为l=mid时,上方mid要加1防止出现死循环
                else r=mid-1;
            }
            cout<<l<<endl;
        }
    }
    return 0;
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值