二分查找(固定套路)

给定一个按照升序排列的长度为 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,
        
	}
}

猜你喜欢

转载自blog.csdn.net/qq_24917263/article/details/127951017