今天做了一道二分题
感觉二分仍需要加强
一个经典题目
求一个递增数组里某个数字出现次数
我们第一想法:暴力
的确可以
边读入边判断是否为我们要的那个数
这种复杂度是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;
}