已知有两个等长的非降序序列S1, S2, 设计函数求S1与S2并集的中位数。有序序列A0,A1,⋯,AN−1的中位数指A(N−1)/2的值,即第⌊(N+1)/2⌋个数(A0为第1个数)。算法
输入样例1:数组
5 1 3 5 7 9 2 3 4 5 6输出样例1:函数
4输入样例2:spa
6 -100 -10 1 1 1 1 -50 0 2 3 4 5输出样例2:设计
1
题目:进阶实验1-3.1 两个有序序列的中位数 (25分)3d
看完题目第一反应是两个集合求并集,再排个序输出中间的数就行了。可是看到数据量10,0000个数,时间限制是200ms。快排时间复杂度是O(nlogn),必定是会超时的。
因此必定有一个更好的算法。
接下来留意到题目中的序列是非降序序列,想到取各自的中位数而后比较。经过比较缩小问题规模的办法。若是办法有效,算法的时间复杂度应该是O(logn),知足评分要求。
那么下面开始验证这个想法。code
思想
咱们首先取序列S1的中位数设为mida,再取序列S2的中位数设为midb。
因为序列S一、S2都是升序排列的。故S1mida左边的数都小于mida,右边的数都大于mida。序列S2同理。
此时比较mida和midb。
因为mida是S1的中位数,midb是S2的中位数。故集合U=S1∪S2
中,大于MAX{mida,midb}
的全部数都不多是中位数。同理可得,集合中小于MIN{mida,midb}
的全部数也都不多是中位数。
经过比较mida和midb的大小,咱们把集合U划分红了两个区间.
即A=[MIN{mida,midb}的右区间, MAX{mida,midb}的左区间]
和∁UA
。
此时问题就被简化成了求集合A的中位数。
然后经过不断的二分查找,A最后必定会变成一个只有2个数的集合。那么根据中位数的定义,此时中位数必然是min{A}
,即两个数中更小的那个。blog
咱们来模拟一下这个过程。排序
这是序列S1,此时mida=5。递归
这是序列S2,此时midb=4。
因为mida > midb,故此时U=S1∪S2
被分红了两个集合,A={1,3,5}∪{4,5,6}
(蓝色)及∁UA
(白色)。
中位数必然在集合A中。由于中位数是排序后位于数列中间,因此它应该在两个升序子序列的中位数的中间。
此时问题就变成了在集合A中取得中位数。白色的∁UA
能够直接抛弃。
递归上述操做,咱们能够逐步迭代集合A。
直到这一步,咱们会遇到一个问题,也是笔者遇到的一个大坑。
此时两个序列中的数字个数都为偶数数,中位数为俩数中小的那个也就是前面那个。若继续按这种方式迭代,接下来的集合会变成这个。
因为{3,5}中,中位数为3,小于4,那么接下来应该取它右边的序列。此时会发现此序列取右边的序列仍是{3,5}!它会形成无限递归或者死循环!
因此分析到这一步咱们发现,应该是要分辨集合中数字的个数为奇数仍是偶数来分别取子序列。最终咱们发现,除了0之外,天然数中最小的偶数是2。在序列长度为2且升序的状况下,中位数直接就是前面那个。
把它扩展到4,那么咱们发现只要抛掉首位两个数,状况就退化成了上述状况。即又一次迭代了集合A。换做到题目中,即直接抛掉偶数序列中的边界数便可。即mida或midb(两个子序列都是偶数则兼有之)。
因此{4,5,6,1,3,5}
以后的迭代出的{4,5,3,5}
是不正确的!
正确迭代方式应该抛掉{4,5,6,1,3,5}
中的mida和midb。
正确的集合A以下。
最终筛选出中位数为4。
下面给出笔者的代码。因为最近在复习C语言,写的是尾递归的版本。
#include <stdio.h> #define MAX_N 100000 /* 二分查找函数声明 aleft a数组左下标,aright a数组右下标*/ int bin_search(int a[], int aleft, int aright, int b[], int bleft, int bright); int main() { int n = 0, a[MAX_N] = {0}, b[MAX_N] = {0}; scanf("%d", &n); for(int i = 0;i<n;i++){ scanf("%d", &a[i]); } for(int i=0;i<n;i++){ scanf("%d", &b[i]); } int mid = bin_search(a, 0, n-1, b, 0, n-1); printf("%d\n", mid); return 0; } int bin_search(int a[], int aleft, int aright, int b[], int bleft, int bright){ int al=0, ar=0, bl=0, br=0; /* 下一步递归的a,b数组下标 */ /* indexa a数组中位数下标 mida a数组中位数的值*/ int indexa = (aleft+aright)/2, indexb = (bleft+bright)/2, mida = a[indexa], midb = b[indexb]; /* 若是俩数组中位数相等 则必是解 */ if(mida == midb){ return mida; } /*若是待查找区间内只有一个数,则小的那个为解*/ if(aleft >= aright && bleft >= bright){ return mida<midb?mida:midb; } if(mida > midb){ bl = indexb; /* 小的取右区间 */ br = bright; ar = indexa; /* 大的取左区间 */ al = aleft; if( (aright-aleft+1) % 2 == 0){ /*偶数个数缩小范围时抛掉当前中位数*/ bl = indexb+1; } }else if(mida < midb){ al=indexa; ar = aright; bl=bleft; br = indexb; if((bright-bleft+1) % 2 == 0){ al=indexa+1; } } return bin_search(a, al, ar, b, bl, br); }
运行状况以下。
看了下最快耗时是25ms左右。并无数量级上的差距。若是把递归改为循环,缓冲输入改为快速输入应该能有差很少的时间耗时,说明此算法应该是目前为止最快的了。
本次题目难度不大,主要锻炼了下写代码态度QAQ。毕竟很久没写代码了。对于边界条件的掌握仍是有些生疏,但愿可以更加严谨。 朋友们有什么问题的也欢迎跟我交流~