原題是力扣上的題目
給定兩個大小爲 m 和 n 的有序數組 nums1 和 nums2。
請你找出這兩個有序數組的中位數,並且要求算法的時間複雜度爲 O(log(m + n))。
你可以假設 nums1 和 nums2 不會同時爲空。
示例 1:
nums1 = [1, 3]
nums2 = [2]
則中位數是 2.0
示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
則中位數是 (2 + 3)/2 = 2.5
題目不算難,但是有個問題,要求時間複雜度爲O(log(m + n)),也就是說,如果我們想先統一排序,再去取中位數的操作是行不通的,因爲那樣的時間複雜度爲O(m+n),我對這個題目思考了很久,如果想要做到O(log(m + n)),那勢必每次循環都需要排除一半的可能,猜到應該是和有序有關,但是基本沒有思路,因爲是有可能存在【1,3,5,7,9】【2,4,6,8】這樣滿足有序的數組,但是我完全想不出來每次操作都排除一半可能的操作方法
想了半天,實在沒有解決思路,就可恥的去看了官方的解法,官方的解法更是讓人頭大,各種數學公式,看的我頭暈眼花,真是讓人頭大,繞過這些鬼話,直接看代碼,終於勉強看懂了
首先讓我們想一想中位數的本質是什麼,就是一個數組中的最中間的數,我們將兩個數組宛若俄羅斯套娃一般排列,中間較小的那個正方形代表較大數組中的全部值,而外面套着的這一圈就是較小數組代表的值,原諒我實在懶得重新畫了,正常來說中間這條線就是中位數
而本題中,在最好的情況下,也確實是這樣,但是最好的情況畢竟也不多見,官方題解的思路也差不多是這樣,如果兩個數組相交的那條線
大於中間這條線,那麼我們可以認爲,這個數顯然不可能是中位數,他太大了,所以我們把較大數組的一部分填充過來,而把較小數組的一部分捨去,相反,我們就認爲這個數太小了,所以我們把較大數組的一部分捨棄過來,而把較小數組的一部分加入,如果不斷操作,最後可能會出現下面這幾種情況
較小的數組都太大了,均不可能滿足中位數的要求,那麼我們只需要根據數組有序的前提,直接得出較大數組[(m+n)/2+1](奇數)就是中位數,相反,如果較小數組擠佔了所有的空間,我們就可以認爲較小數組[(m+n)/2+1](奇數)就是中位數,但是現實中顯然不太可能出現這兩種極端的情況,我們需要考慮一種更復雜的情況,如果較大數組和較小數組互相重疊,也就是我們之前提到的【1,3,5,7,9】【2,4,6,8】的情況,這種情況下,問題顯然複雜的多,會呈現這種情況
中位數可能落在兩個數組任意一個上,我們不能在簡單的說[(m+n)/2+1]就是中位數了,那有沒有辦法呢,其實是有的,比如出現上面這種混亂的情況,我們只需要獲取中位數在哪就行了,有兩種可能,中位數在較小數組的未被排除的最後一位,或是在較大數組的未被排除的最後一位,我們只需要用較小數組的可能數組減去總的可能數組,就可以知道較大數組未被排除的最後一位數,然後獲取這兩個數,哪個數大,那麼這個數就是中位數(奇數),因爲更大的數更接近中位數,最接近的那個數就是中位數本身,官方算法的思路就是如此
public double findMedianSortedArrays(int[] A, int[] B) {
int m = A.length;
int n = B.length;
將a轉爲爲那個較小的數組
if (m > n) { // to ensure m<=n
int[] temp = A; A = B; B = temp;
int tmp = m; m = n; n = tmp;
}
imin表示較小數組的最小位置
imax表示較小數組的最大位置
halfLen表示中位數出現的最大半徑(即,兩個數組之和的一半,向上取整),若要取(m+n)/2+1需要-1
int iMin = 0, iMax = m, halfLen = (m + n + 1) / 2;
while (iMin <= iMax) {
i表示當前較小數組所佔的可能數
int i = (iMin + iMax) / 2;
j表示較大數組所佔的可能數
int j = halfLen - i;
說明當前的較大數組的值太大了,需要填充一部分較小數組
if (i < iMax && B[j-1] > A[i]){
iMin = i + 1; // i is too small
}
說明當前的較大數組的值太小了,需要捨棄一部分較小數組
else if (i > iMin && A[i-1] > B[j]) {
iMax = i - 1; // i is too big
}else { // i is perfect
int maxLeft = 0;
若較小數組被全部捨棄,直接到較大數組中尋找[(m+n)/2+1]
if (i == 0) { maxLeft = B[j-1]; }
若較大數組被全部捨棄,直接到較小數組中尋找[(m+n)/2+1]
else if (j == 0) { maxLeft = A[i-1]; }
否則比較兩個數哪個纔是真正的中位數
else { maxLeft = Math.max(A[i-1], B[j-1]); }
if ( (m + n) % 2 == 1 ) { return maxLeft; }
下面幾乎相同,只不過取的是偶數的另一半,此時正好相反,哪個數小,哪個數纔是另一半
因爲此時需要求的是大於中位數的數,所以越小靠近前面的中位數
int minRight = 0;
if (i == m) { minRight = B[j]; }
else if (j == n) { minRight = A[i]; }
else { minRight = Math.min(B[j], A[i]); }
return (maxLeft + minRight) / 2.0;
}
}
return 0.0;
}