【每日算法】基础算法——整数二分查找[数的范围](五)

题目内容

给定一个按照升序排列的长度为n的整数数组,以及 q 个查询。
对于每个查询,返回一个元素k的起始位置和终止位置(位置从0开始计数)。
如果数组中不存在该元素,则返回“-1 -1”。

输入格式

第一行包含整数n和q,表示数组长度和询问个数。
第二行包含n个整数(均在1~10000范围内),表示完整数组。
接下来q行,每行包含一个整数k,表示一个询问元素。

输出格式

共q行,每行包含两个整数,表示所求元素的起始位置和终止位置。
如果数组中不存在该元素,则返回“-1 -1”。

数据范围

1≤n≤100000
1≤q≤10000
1≤k≤10000

输入样例

6 3
1 2 2 3 3 4
3
4
5

输出样例

3 4
5 5
-1 -1

题解

本题采用整数二分查找的思想求解。
首先,二分查找可以根据某个具体的性质,将某个区间切分为两部分,一部分满足性质,另一部分不满足性质,那么二分查找是可以找到其满足性质的边界点,这是二分查找的核心本质。因此,二分查找,就是求根据某个性质而产生的边界点。
那么,我们可以有如下思考,这里给出一张图,根据图来进行说明。
二分查找示意图
L和R代表序列的左右边界,黄色区间和红色区间是按照是否满足某个性质而产生的两段,若求黄色和红色的分界点,且设置黄色区间不满足分界性质,红色区间满足分界性质,思考如下:

  • 当求黄色部分的分界点时:
    • 假设mid=(L+R+1)/2,check函数为判断某个点是否满足黄色区间的性质,返回true或false。( 这里mid的定义(L+R+1)/2中的+1是避免出现死循环,具体可以思考当l=r-1时,若不加上1,就会出现死循环
    • 若check(mid)为true,则说明,mid这个点满足条件,因此,分界点一定在其右边,因此接下来需要二分的区间应该是[mid,r],将l=mid;若check(mid)为false,则说明,mid这个点不满足条件,因此分界点一定在其左边,因此接下来需要二分的区间应该是[l,mid-1],r=mid-1。
  • 当求红色部分的分界点时:
    • 假设mid=(L+R)/2, check函数为判断某个点是否满足红色区间的性质,返回true或false。
    • 若check(mid)为true,则说明,mid这个点满足这个性质,因此分界点应该在mid点的左边即二分的区间应该是[l,mid],r=mid;若check(mid)为false,则说明,mid这个点不满足这个性质,因此分界点应该在mid点的右边,即二分的区间应该是[mid+1,r],l=mid+1。

回到题中,我们可以进行如下思考:
因为题目中求某个数的范围,因此,边界有两个,一个是大于等于x,一个是小于等于x,对每个性质分别使用二分查找的方法求出分界点即可。

代码

#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;
}
Author: Frederic Niu
Link: https://www.fredericniu.cn/2020/11/17/【每日算法】基础算法——整数二分查找-数的范围-(五)/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
我的公众号