给定一个按照升序排列的长度为 nn 的整数数组,以及 qq 个查询。
对于每个查询,返回一个元素 kk 的起始位置和终止位置(位置从 00 开始计数)。
如果数组中不存在该元素,则返回 -1 -1
。
输入格式
第一行包含整数 nn 和 qq,表示数组长度和询问个数。
第二行包含 nn 个整数(均在 1∼100001∼10000 范围内),表示完整数组。
接下来 qq 行,每行包含一个整数 kk,表示一个询问元素。
输出格式
共 qq 行,每行包含两个整数,表示所求元素的起始位置和终止位置。
如果数组中不存在该元素,则返回 -1 -1
。
数据范围
1≤n≤1000001≤n≤100000
1≤q≤100001≤q≤10000
1≤k≤100001≤k≤10000
输入样例:
6 3
1 2 2 3 3 4
3
4
5
输出样例:
3 4
5 5
-1 -1
思路分析:二分查找虽然思想很简单,但细节非常多,一不小心就会出错
现给出一种二分的套路,利用划分大区间和返回大区间边界处的左右指针来控制整个二分
先给出伪代码
关于几点细节的说明:这个伪代码将二分分为两个区间即蓝红区间
1.为什么设定l = -1,r = n,因为如果不这样设置的话,如果说整个区间都是蓝色(满足某种性质)或者红色,指针指向的地方就会有问题。因此设定为-1和n,同时也符合指针本身的含义,比如说开头和末尾的左边和右边肯定是-1和n
2.会不会陷入死循环?不会,比方说 l +1=r,相当于红蓝区间末尾连在一起,会退出循环,如果l+2=r相当于末尾中间多了一个数,之后为自动变为l+1=r的状态,l+3=r会回归到l+2=r,l+2=r会回归到l+1=r,所以不会
3.会不会越界?我们来看m的最小值,要使m最小,那么l和r最小,且同时要满足进入循环条件,所以说l=-1,r = 1,m=0,m取最大的值时l和r取最大,l = n-2,r = n,m = n-1.不难看出
4.为什么设定l=m,r=m,因为如果说设定为l = m+1,如果m指向蓝色区间最后一个,l就会指向红色区间,明显有误,为了便于理解和统一,我们全都设定为l = m,或者r= m
接下来的核心就是划分区间了,我们的思想不应该是传统的对半切,这样容易出错
我们这样想,当m满足isblue的时候,我们将m及其前面的所有数全部化进蓝色区间,通过窗口滑动来想更容易对,之后根据最开始划分的区间确定返回l还是r,代码如下
#include <stdio.h>
int arr[100001];
int judgerel(int k,int n){
int l = -1,r =n;
while(l+1!=r){
int m = (l+r)/2;
if (arr[m]<=k){
l = m;
}
else r = m;
}
return l;
}
int judgerer(int k,int n){
int l = -1,r=n;
while(l+1!=r){
int m = (l+r)/2;
if(arr[m]<k){
l = m;
}
else r = m;
}
return r;
}
int main(){
int n;
int q;
scanf("%d %d",&n,&q);
for(int i =0;i<n;i++){
scanf("%d",&arr[i]);
}
int k;
for(int i = 0;i<q;i++){
scanf("%d",&k);
int ansmax = judgerel(k,n);//返回左指针的一定是最后一次
int ansmin = judgerer(k,n);//返回右指针的一定是第一次遇到的
if(ansmin>ansmax){
printf("-1 -1\n");
}
else
printf("%d %d\n",ansmin,ansmax);
//如果返回右指针:区间为 122 334
//如果返回左指针 区间为12233 4,
}
}