2019.08.08 二分思想

今天做了一道二分题

感觉二分仍需要加强

一个经典题目

求一个递增数组里某个数字出现次数

我们第一想法:暴力

的确可以

边读入边判断是否为我们要的那个数

这种复杂度是O(n)

有没有更高效算法呢?

二分!!!

我们可以二分查找数组中这个数出现的第一个位置和最后一个位置

数量不就得出来了吗

那么怎么二分呢?

我们知道二分的形式无非就是

while(l<r){
	int mid=(l+r(+1))>>1;
	if(条件){
		l=mid(+1);
	}else {
		r=mid(-1);
	}
}

那么其中要不要加+1还是-1,什么时候加呢?

我们还没有一个固定思路

可以先假设两个相邻相同元素

…… 2 2 ……

如果我们要找出现2的第一个位置

那么我们的a[mid]==2

我们应该修改l还是r呢?

答案当然是r
因为我们不敢修改l,一修改就可能把第一个位置跳过了

那么r变成啥呢?

r=mid-1 吗?还是直接r=mid?

这里我们选择r=mid

因为mid可能就在第一个数字的位置,我们不能跳过

接下来考虑是(l+r+1)>>1还是(l+r)>>1呢?

我们上面的两个相邻相同元素就可以用上了

如果是选用前者,那么如果中间有两个数,会选择右边的数

而我们想揪出第一个数

得选左边的数叭

所以我们选择了(l+r)>>1

接下来想想如果a[mid]<k咋办?

我们在这种情况下不能修改r,只能修改l=mid(+1)

是否要+1呢?

要!!!

因为mid位置绝对不可能是第一个位置

所以直接可以跳过

于是乎,剩下最后的a[mid]>k时

显然同a[mid]==k的情况

代码如下

	int pre,last,l,r;
	for(l=0,r=n;l<r;){
		int mid=(l+r)>>1;
		if(a[mid]>=k){
			r=mid;
		}else {
			l=mid+1;
		}
	}
	pre=r;
	

为啥边界是左闭右开呢

因为我们的右边界一定要更新!

最后的结果是看r

我也不太确定,这里的结果用l和r都是一样的叭

然后要取最后一个数的话,同理

所有代码如下

#include<bits/stdc++.h>
using namespace std;
int a[10000007];
inline int read(){
	int x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		ch=getchar();
	} 
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x;
}
int main(){
	//freopen("x3.in","r",stdin);
	//freopen("x3.out","w",stdout);
	int n,k,ans=0;
	n=read(),k=read();
	for(int i=0;i<n;i++){
		a[i]=read();
		if(a[i]==k){
			ans++;
		}
	}
	int pre,last,l,r;
	for(l=0,r=n;l<r;){
		int mid=(l+r)>>1;
		if(a[mid]>=k){
			r=mid;
		}else {
			l=mid+1;
		}
	}
	pre=r;
	
	for(l=-1,r=n-1;l<r;){
		int mid=(l+r+1)>>1;
		if(a[mid]<=k){
			l=mid;
		}else {
			r=mid-1;
		}
	}
	last=l;
	printf("%d",last-pre+1);
	return 0;
}
发布了68 篇原创文章 · 获赞 11 · 访问量 5592

猜你喜欢

转载自blog.csdn.net/weixin_43982216/article/details/98873589