中位数问题(二分答案)

问题描述:

给定一个 N 个数的数组 cat[i],并用这个数组生成一个新数组 ans[i]。新数组定义为对于任意的 i, j 且 i != j,均有 ans[] = abs(cat[i] -cat[j]),
1 <= i < j <= N。试求出这个新数组的中位数,中位数即为排序之后 (len+1)/2 位置对应的数字,‘/’ 为下取整。

input:
多组输入,每次输入一个 N,表示有 N 个数,之后输入一个长度为 N 的序列 cat, cat[i] <= 1e9 , 3 <= n <= 1e5
output:
输出新数组 ans 的中位数

样例输入:

4
1 3 2 4
3
1 10 2

样例输出:

1
8

解题思路:

首先我们可以考虑暴力做法,枚举i和j,得到数组ans,然后求中位数,但是复杂度为 n 2 n^2 n2,不能接受。那么,如果我们已知了一个答案的区间,并且这个区间是单调的,那么我们就可以二分答案,对每一次二分得到的数的名次与中位数的名次比较,如果名次小于中位数,则二分值比中位数要小,如果名次大于中位数,则二分值比中位数要大,如果名次等于中位数,那它就是中位数。所以我们可以通过二分答案的方法,来降低算法复杂度。

如果我们得到了一个二分值P,怎么去求它的名次呢,如果在数组ans中求,那么和暴力方法没有区别,考虑到有绝对值,首先我们可以对cat数组进行排序,去掉绝对值号,那么ans中的数组元素就是cat[j]-cat[i],其中ii,所以名次等于每一次j的最大值减去i的累加和。在求出名次之后,和中位数名次进行比较,继续二分。这样通过两次二分,就能找到中位数,算法的复杂度为 n l o g 2 n nlog^2n nlog2n

由于得到的新数组ans中可能有重复值,按照这种算法来计算,即使它是中位数,它的名次依然有可能大于中位数的名次,所以在进行二分时,名次大于等于中位数的名次当成一种情况处理。

代码:

#include
#include
#include 
#include
#include
using namespace std;
int search2(int x,int left,int right,int ca[])
//查找最后一个大于等于P+cat[i]的位置
{
	int ans=-1;
	while(left<=right)
	{
		int mid=(left+right)/2;
		if(ca[mid]<=x)
		{
			ans=mid;
			left=mid+1;
		}		
		else
			right=mid-1;
	}
	return ans;
}
int search(int left,int right,int ca[],int number)
{//求解中位数,采用二分
	int to=(number*(number-1)/2+1)/2;//中位数的位置
	int ans=-1;
	while(left<=right)
	{
		int rank=0;
		int mid=(left+right)/2;
		for(int i=0;i<number;i++)//枚举所有i,找到符合条件的j
		{
			int temp=ca[i]+mid;
			int num=search2(temp,0,number-1,ca);
			if(num!=-1)
				rank+=num-i;
		}
		if(rank<to)
			left=mid+1;
		else 
		{//rank>=to当成一种情况,具体原因思路里提到过了
			ans=mid;
			right=mid-1;
		}
		
	}
	return ans;
}
int main()
{
	int n;
	while(scanf("%d", &n) != EOF)
	{
		int cat[100001];
		for(int i=0;i<n;i++)
			scanf("%d",&cat[i]);
		sort(cat,cat+n);//排序
		int middle=search(0,cat[n-1]-cat[0],cat,n);
		cout<<middle<<endl;
	}
}

你可能感兴趣的:(中位数问题(二分答案))