二分查找:寻找中位数
给定一个 N 个数的数组 c[i],并用这个数组生成一个新数组 ans[i]。新数组定义为对于任意的 i, j 且 i != j,均有 ans[] = abs(c[i] - c[j]),1 <= i < j <= N。试求出这个新数组的中位数,中位数即为排序之后 (len+1)/2 位置对应的数字,’/’ 为下取整。
多组输入,每次输入一个 N,表示有 N 个数,之后输入一个长度为 N 的序列 cat, cat[i] <= 1e9 , 3 <= n <= 1e5, 输出新数组的中位数
sample input:
4
1 3 2 4
3
1 10 2
sample output:
1
8
思路:
- 首先想到的解决方式肯定是暴力,先枚举出所有的新数组元素,然后给数组排序,再根据位置找到中位数的值。由于原来的数字有n,进行减法的绝对值之后就会有Cn2个数,暴力绝绝对对会超时
- 根据中位数的性质我们来进行简化,中位数的特征是它前面的数字与后面数字的个数是相同的,所以第一种想法是计算出最终数组中中位数的位置(从零开始所以偶数/2,奇数/2+1),然后定义左边界、右边界和中值。不断进行二分的查找,如果查找范围在中值左边,那么right=middle-1,再对左边进行查找;查找范围在右边,那么left=middle+1,再对右边进行查找。根据自己的结束条件来判定最终的结果是输出right还是left;
#include <iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int a[100011];
int n;
int ans;
//int ans[1000000];
int find(int mid)
{
int count=0,temp=0;
for(int i=0;i<n;i++)
{
while(temp<n&&a[temp]-a[i]<mid)//判断差值和mid的位置
temp++;//遍历
count+=temp-i-1;
}
if(count>=ans)//是否符合条件
return 1;
else return 0;
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
for(int i=0;i<n;i++)
//cin>>a[i];
scanf("%d",&a[i]);
int num=n*(n-1)/2;
if(num%2==0) ans=num/2;
else ans=num/2+1;
sort(a,a+n);
int l=0,m=0,r=a[n-1]-a[0];
while(r-l>=0)
{
m=(r+l)/2;
if(find(m)==1)
r=m-1;
else l=m+1;
}
//cout<<r<<endl;
printf("%d\n",r);
}
return 0;
}
- 当然,对于二分插值的查找,可以使用upper_bound和lower_bound函数进行计算。两个函数都是在左闭右开的有序区间之内进行二分的查找。
- 对于upper_bound来说返回被查序列中第一个大于查找值的指针,对于lower_bound是返回被查序列中第一个大于等于查找值的指针。
- 计算出原来数组的中位数,通过原来数组的中位数mid,用来判断新数组中中位数的位置
- 如果原数组中a[j]>a[i]+mid (j>i),那么就说明这个值减去a[i]形成的新数列中的值比mid还要大,计算所有mid大的值,如果这个数比新数列的一半小,说明mid取大了;反之取小了。
- 根据上面的情况,改变left,right的位置,从而得出最终的middle值。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int a[1000011];
int ans;
int n;
long long num;
bool find(int mid)
{
int count=0;
for(int i=0;i<n;i++)
count+=a+n-lower_bound(a+i+1,a+n,a[i]+mid);//返回第一个大于等于x[i+mid值得下标
//count计数所有差值比mid大得数列
return count<= num/2;//count比mid大的个数
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
//cin>>a[i];
}
num=n*(n-1)/2;
sort(a,a+n);
int left=0,right=a[n-1];
while(right-left>=0)
{
int mid=(left+right)/2;
if(find(mid)) right=mid-1;
else left=mid+1;
}
printf("%d\n",right);
//cout<<right<<endl;
}
return 0;
}
- 这道题的坑点在哪呢?可以看一下上面两份代码的相似点是什么都注释掉了cin和cout。没错,这道题如果使用cin/out就会TLE,一定要用scanf和printf。原因是cin,cout在进行输入输出的时候都会先将内容放入缓存,时间开销会大。要么不用,要么关掉同步
- ios::sync_with_stdio(false);
cout.tie(NULL);
随手加上正能量!