問題:兩個已經排好序的數組,找出兩個數組合並后的中位數(如果兩個數組的元素數目是偶數,返回上中位數)。
感覺這種題目挺難的,尤其是將算法完全寫對。因為當初自己微軟面試的時候遇到了,但是沒有想出來思路。看網上寫了一堆解法,但是將思路說得非常清楚的少之又少。
有兩種思路,一個是算法導論里面的,一個是求解k大元素。建議使用下面第二種思路,代碼少不容易出錯。
下面的內容摘自:https://blog.csdn.net/hackbuteer1/article/details/7584838
求解中位數,算法導論上面的分析是這樣的:
Say the two arrays are sorted and increasing, namely A and B.
It is easy to find the median of each array in O(1) time.
Assume the median of array A is m and the median of array B is n. Then,
1、If m==n,then clearly the median after merging is also m,the algorithm holds.
2、If m<=n,then reserve the half of sequence A in which all numbers are greater than m,also reserve the half of sequence B in which all numbers are smaller than n.
Run the algorithm on the two new arrays。
3、If m>n,then reserve the half of sequence A in which all numbers are smaller than m,also reserve the half of sequence B in which all numbers are larger than n.
Run the algorithm on the two new arrays。
Time complexity: O(logn)
下面,我們來畫個圖,分析一下這個思路:
我們先來分析看看: 想到對數的效率,首先想到的就是二分查找,對於這個題目二分查找的意義在哪里呢?
我們找到了A[n/2] 和 B[n/2]來比較,
1、如果他們相等,那樣的話,我們的搜索結束了,因為答案已經找到了A[n/2]就肯定是排序后的中位數了。
2、如果我們發現B[n/2] > A[n/2],說明什么,這個數字應該在 A[n/2]->A[n]這個序列里面, 或者在 B[1]-B[n/2]這里面。 或者,這里的或者是很重要的, 我們可以說,我們已經成功的把問題變成了在排序完成的數組A[n/2]-A[n]和B[0]-B[n/2]里面找到合並以后的中位數, 顯然遞歸是個不錯的選擇了。
3、如果B[n/2] < A[n/2]呢?顯然就是在A[0]-A[n/2]和B[n/2]-B[n]里面尋找了。
在繼續想, 這個遞歸什么時候收斂呢?當然一個case就是相等的值出現, 如果不出現等到這個n==1的時候也就結束了。
照着這樣的思路, 我們比較容易寫出如下的代碼, 當然邊界的值需要自己思量一下(遞歸代碼如下):
// 兩個長度相等的有序數組尋找中位數
int Find_Media_Equal_Length(int a[] , int b[] , int length)
{
if(length == 1)
{
return a[0] > b[0] ? b[0] : a[0];
}
int mid = (length-1)/2; //奇數就取中間的,偶數則去坐標小的
if(a[mid] == b[mid])
return a[mid];
else if(a[mid] < b[mid])
{
return Find_Media_Equal_Length(&a[length-mid-1] , &b[0] , mid+1); //偶數則取剩下的length/2,奇數則取剩下的length/2+1
//return Find_Media_Equal_Length(a+length-mid-1 , b , mid+1);
}
else
{
return Find_Media_Equal_Length(&a[0] , &b[length-mid-1] , mid+1);
//return Find_Media_Equal_Length(a , b+length-mid-1 , mid+1);
}
}
二:馬上有人說那不定長的怎么辦呢?一樣的,我們還是來畫個圖看看:
因為一個常識:如果我們去掉數組比中位數小的k個數,再去掉比中位數大的k個數,得到的子數組的中位數和原來的中位數相同。
一樣的, 我們還是把這個兩個數組來比較一下,不失一般性,我們假定B數組比A數組長一點。A的長度為n, B的長度為m。比較A[n/2]和B[m/2] 時候。類似的,我們還是分成幾種情況來討論:
a、如果A[n/2] == B[m/2],那么很顯然,我們的討論結束了。A[n/2]就已經是中位數,這個和他們各自的長度是奇數或者偶數無關。
b、如果A[n/2] < B[m/2],那么,我們可以知道這個中位數肯定不在[A[0]---A[n/2])這個區間內,同時也不在[B[m/2]---B[m]]這個區間里面。這個時候,我們不能沖動地把[A[0]---A[n/2])和[B[m/2]---B[m]]全部扔掉。我們只需要把[B[m-n/2]---B[m]]和[A[0]---A[n/2])扔掉就可以了。(如圖所示的紅色線框),這樣我們就把我們的問題成功轉換成了如何在A[n/2]->A[n]這個長度為 n/2 的數組和 B[1]-B[m-n/2]這個長度為m-n/2的數組里面找中位數了,問題復雜度即可下降了。
c、只剩下A[n/2] > B[m/2],和b類似的,我們可以把A[n/2]->A[n]這塊以及B[1]->B[n/2]這塊扔掉了就行,然后繼續遞歸。
我們也可以寫出如下的代碼:
// 兩個長度不相等的有序數組尋找中位數
int Find_Media_Random_Length(int a[] , int lengtha , int b[] , int lengthb)
{
int mida = lengtha/2;
int midb = lengthb/2;
int l = (mida <= midb) ? mida : midb;
if(lengtha == 1)
{
if(lengthb % 2 == 0)
{
if(a[0] >= b[midb])
return b[midb];
else if(a[0] <= b[midb-1])
return b[midb-1];
return a[0];
}
else
return b[midb];
}
else if(lengthb == 1)
{
if(lengtha % 2 == 0)
{
if(b[0] >= a[mida])
return a[mida];
else if(b[0] <= a[mida-1])
return a[mida-1];
return b[0];
}
else
return a[mida];
}
if(a[mida] == b[midb])
return a[mida];
else if(a[mida] < b[midb])
return Find_Media_Random_Length(&a[mida] , lengtha-l , &b[0] , lengthb-l);
else
return Find_Media_Random_Length(&a[0] , lengtha-l , &b[midb] , lengthb-l);
}
代碼果然很蛋疼。。。。
摘自:https://blog.csdn.net/yutianzuijin/article/details/11499917
根據算法導論中的方法,但是該方法會存在無窮多的邊界細節問題,而且擴展也不見得正確,這個可從各網頁的評論看出,非常不建議大家走這條路。
最后從medianof two sorted arrays中看到了一種非常好的方法。原文用英文進行解釋,在此我們將其翻譯成漢語。該方法的核心是將原問題轉變成一個尋找第k小數的問題(假設兩個原序列升序排列),這樣中位數實際上是第(m+n)/2小的數。所以只要解決了第k小數的問題,原問題也得以解決。
首先假設數組A和B的元素個數都大於k/2,我們比較A[k/2-1]和B[k/2-1]兩個元素,這兩個元素分別表示A的第k/2小的元素和B的第k/2小的元素。這兩個元素比較共有三種情況:>、<和=。如果A[k/2-1]<B[k/2-1],這表示A[0]到A[k/2-1]的元素都在A和B合並之后的前k小的元素中。換句話說,A[k/2-1]不可能大於兩數組合並之后的第k小值,所以我們可以將其拋棄。
證明也很簡單,可以采用反證法。假設A[k/2-1]大於合並之后的第k小值,我們不妨假定其為第(k+1)小值。由於A[k/2-1]小於B[k/2-1],所以B[k/2-1]至少是第(k+2)小值。但實際上,在A中至多存在k/2-1個元素小於A[k/2-1],B中也至多存在k/2-1個元素小於A[k/2-1],所以小於A[k/2-1]的元素個數至多有k/2+ k/2-2,小於k,這與A[k/2-1]是第(k+1)的數矛盾。
當A[k/2-1]>B[k/2-1]時存在類似的結論。
當A[k/2-1]=B[k/2-1]時,我們已經找到了第k小的數,也即這個相等的元素,我們將其記為m。由於在A和B中分別有k/2-1個元素小於m,所以m即是第k小的數。(這里可能有人會有疑問,如果k為奇數,則m不是中位數。這里是進行了理想化考慮,在實際代碼中略有不同,是先求k/2,然后利用k-k/2獲得另一個數。)
通過上面的分析,我們即可以采用遞歸的方式實現尋找第k小的數。此外我們還需要考慮幾個邊界條件:
如果A或者B為空,則直接返回B[k-1]或者A[k-1];
如果k為1,我們只需要返回A[0]和B[0]中的較小值;
如果A[k/2-1]=B[k/2-1],返回其中一個;
最終實現的代碼為:
double findKth(int a[], int m, int b[], int n, int k)
{
//always assume that m is equal or smaller than n
if (m > n)
return findKth(b, n, a, m, k);
if (m == 0)
return b[k - 1];
if (k == 1)
return min(a[0], b[0]);
//divide k into two parts
int pa = min(k / 2, m), pb = k - pa;
if (a[pa - 1] < b[pb - 1])
return findKth(a + pa, m - pa, b, n, k - pa);
else if (a[pa - 1] > b[pb - 1])
return findKth(a, m, b + pb, n - pb, k - pb);
else
return a[pa - 1];
}
class Solution
{
public:
double findMedianSortedArrays(int A[], int m, int B[], int n)
{
int total = m + n;
if (total & 0x1)
return findKth(A, m, B, n, total / 2 + 1);
else
return (findKth(A, m, B, n, total / 2)
+ findKth(A, m, B, n, total / 2 + 1)) / 2;
}
};
我們可以看出,代碼非常簡潔,而且效率也很高。在最好情況下,每次都有k一半的元素被刪除,所以算法復雜度為logk,由於求中位數時k為(m+n)/2,所以算法復雜度為log(m+n)。
如果上面的思路沒有看懂的話,https://www.cnblogs.com/voidsky/p/5373982.html 這里面使用切割的思路特別容易明白!
問題介紹
這是個超級超級經典的分治算法!!這個問題大致是說,如何在給定的兩個有序數組里面找其中的中值,或者變形問題,如何在2個有序數組數組中查找Top K的值(Top K的問題可以轉換成求第k個元素的問題)。這個算法在很多實際應用中都會用到,特別是在當前大數據的背景下。
我覺得下面的這個思路特別好,特別容易理解!!請按順序看。是來自leetcode上的stellari英文答案,我整理並自己修改了一下。
預備知識
先解釋下“割”
我們通過切一刀,能夠把有序數組分成左右兩個部分,切的那一刀就被稱為割(Cut),割的左右會有兩個元素,分別是左邊最大值和右邊最小值。
我們定義L = Max(LeftPart),R = Min(RightPart)
Ps. 割可以割在兩個數中間,也可以割在1個數上,如果割在一個數上,那么這個數即屬於左邊,也屬於右邊。(后面講單數組中值問題的時候會說)
比如說[2 3 5 7]這個序列,割就在3和5之間
[2 3 / 5 7]
中值就是(3+5)/2 = 4
如果[2 3 4 5 6]這個序列,割在4上,我們可以把4分成2個
[2 3 (4/4) 5 7]
中值就是(4+4)/2 = 4
這樣可以保證不管中值是1個數還是2個數都能統一運算。
割和第k個元素
對於單數組,找其中的第k個元素特別好做,我們用割的思想就是:
常識1:如果在k的位置割一下,然后A[k]就是L。換言之,就是如果左側有k個元素,A[k]屬於左邊部分的最大值。(都是明顯的事情,這個不用解釋吧!)
雙數組
我們設:
CiCi為第i個數組的割。
LiLi為第i個數組割后的左元素.
RiRi為第i個數組割后的右元素。
圖片特么粘貼不上!!!
如何從雙數組里取出第k個元素
- 首先Li<=RiLi<=Ri是肯定的(因為數組有序,左邊肯定小於右邊)
- 如果我們讓L1<=R2L1<=R2 && L2<=R1L2<=R1
- 那么左半邊 全小於右半邊,如果左邊的元素個數相加剛好等於k,那么第k個元素就是Max(L1,L2),參考上面常識1。
- 如果 L1>R2,說明數組1的左邊元素太大(多),我們把C1減小,把C2增大。L2>R1同理,把C1增大,C2減小。
假設k=3
對於
[1 4 7 9][1 4 7 9]
[2 3 5][2 3 5]
設C1 = 2,那么C2 = k-C1 = 1
[1 4/7 9][1 4/7 9]
[2/3 5][2/3 5]
這時候,L1(4)>R2(3),說明C1要減小,C2要增大,C1 = 1,C2=k-C1 = 2
[1/4 7 9][1/4 7 9]
[2 3/5][2 3/5]
這時候,滿足了L1<=R2L1<=R2 && L2<=R1L2<=R1,第3個元素就是Max(1,3) = 3。
如果對於上面的例子,把k改成4就恰好是中值。