已知有兩個等長的非降序序列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。畢竟很久沒寫代碼了。對於邊界條件的掌握仍是有些生疏,但願可以更加嚴謹。 朋友們有什麼問題的也歡迎跟我交流~