最大相邻差值 数学 桶排序
题意:给出一串无序的数列,编写一个O(n)的算法求解排序后相邻两个数的最大差值。
解法:
nlog(n)的算法大家应该很容易就能想到,只要排序一下再扫描一遍即可。但是为了构造O(n)的算法,肯定不能使用常规排序算法,我们先考虑一下桶排序(非正解),开一个能囊括数值范围的数组,把数据作为下标放进去,再扫描一遍,算出数据之间的最大间隔便是答案了。这样虽然可以做出这题,但是在数据之间的差值非常大的时候,数组往往需要开到非常大,其可能比直接排个序的开销还要大。
为了优化刚刚提到的算法,我们先直观地感受看一下桶排序的流程。
其可以看成最大值和最小值构成的区间被数组其他的数拆分成了若干个子区块,而我们所求的就是最大的子区块。我们可以发现一个规律,最大的子区块长度一定大于等于(max-min)/n。
从数学角度推导的话,如果最大的子区块也小于(max-min)/n,那么所有子区块加在一起肯定不足max-min,显然是不成立的。
所以像1~2,7~9这样的区块,显然不会对答案产生贡献。那么我们将桶排序优化一下,让每个桶里存的数之间的距离都不足(max-min)/n,这样就只用考虑桶与相邻的桶之间的差值即可。
为了构造这样的桶排序,我们先将(max-min)/n向上取整作为除数d,然后将每个数v根据(v-min)/d得到的坐标放入相应的桶中(有人会说(max-min)/n向上取整导致桶内涵盖的区间变大,会不会使得原先我们的假定无效呢?这个自己动手算一算就可以了,向下取整当然也可以,但是就要分出更多的桶了),然后我们只要遍历每组相邻的桶,计算左边的最大值和右边最小值的差值,找到最大的差值即是答案了。因为这里只需要计算最大最小值,所以并不需要真的做放入桶的操作,只要更新一下最值即可。
#include <iostream> #include <vector> #include <queue> #define LL long long using namespace std; const LL INF=1e31-1; const int N=10000007; LL a[N]; LL mxt[N]; LL mit[N]; bool emp[N]; LL n; int main() { //freopen("in.txt","r",stdin); //freopen("out1.txt","w",stdout); cin.sync_with_stdio(false); while(cin>>n) { LL mx=-1,mi=INF; fill(mxt,mxt+n+1,-1); fill(mit,mit+n+1,INF); fill(emp,emp+n+1,true); for(int i=0;i<n;i++) { cin>>a[i]; mx=max(mx,a[i]); mi=min(mi,a[i]); } LL avg=(mx-mi)/n+((mx-mi)%n!=0); if(avg==0) { cout<<0<<endl; continue; } for(int i=0;i<n;i++) { LL pos=(a[i]-mi)/avg; mxt[pos]=max(mxt[pos],a[i]); mit[pos]=min(mit[pos],a[i]); emp[pos]=false; } LL dis=0; int l=-1; for(int i=0;i<n+1;i++) { if(!emp[i]) { if(l!=-1) dis=max(dis,mit[i]-mxt[l]); l=i; } } cout<<dis<<endl; } }