第4題
給定兩個大小為 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
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/median-of-two-sorted-arrays
中位數定義
將一個集合划分為兩個長度相等的子集,其中一個子集中的元素總是大於另一個子集中的元素。
解題思路
將數組進行切。
-
長度為 m 的數組,有 0 到 m 總共 m + 1 個位置可以切。
-
把數組 A 和數組 B 分別在 i 和 j 進行切割。
-
將 i 的左邊和 j 的左邊組合成「左半部分」,將 i 的右邊和 j 的右邊組合成「右半部分」。
當 A 數組和 B 數組的總長度是偶數時,如果我們能夠保證
-
左半部分的長度等於右半部分 \(i+j=m-i+n-j\), 也就是 \(j=(m+n)/2-i\)
-
左半部分最大的值小於等於右半部分最小的值 \(max(A[i-1],B[j-1])<=min(A[i],B[j])\)
那么,中位數就可以表示如下
\((左半部分最大值+右半部分最小值)/2\)==>\((max(A[i-1],B[j-1])+min(A[i],B[j]))/2\)
當 A 數組和 B 數組的總長度是奇數時,如果我們能夠保證
-
左半部分的長度比右半部分大1,即\(i+j=m-i+n-j+1\) 也就是 \(j=(m+n+1)/2-i\)
-
左半部分最大的值小於等於右半部分最小的值 \(max(A[i-1],B[j-1]) <=min(A[i],B[j])\)
那么,中位數就是
左半部分最大值,也就是左半部比右半部分多出的那一個數==>\(max(A[i-1],B[j-1])\)
對以上條件進行分析
第一個條件
我們其實可以合並為 \(j=(m+n+1)/2-i\),因為如果\(m+n\) 是偶數,由於我們取的是 int 值,所以加 1 也不會影響結果。當然,由於 \(0<=i<=m\),為了保證 \(0<=j<=n\),我們必須保證 \(m<=n\)。
-
\(m≤n,i<m,j=(m+n+1)/2−i≥(m+m+1)/2−i>(m+m+1)/2−m=0\)
-
\(m≤n,i>0,j=(m+n+1)/2−i≤(n+n+1)/2−i<(n+n+1)/2=n\)
最后一步由於是 int 間的運算,所以 \(1/2=0\)
第二個條件
奇數和偶數的情況是一樣的,我們進一步分析。
為了保證 \(max(A[i-1],B[j-1])<=min(A[i],B[j])\),因為 A 數組和 B 數組是有序的,所以 $A[i-1]<=A[i],B[i-1]<=B[i] 這是一定的,所以我們只需要保證 \(B[j-1]<=A[i]和A[i-1]<=B[j]\),即我們要分兩種情況討論:
-
\(B[j-1]>A[i]\),並且為了不越界,要保證 \(j!= 0,i!=m\),此時很明顯,我們需要增加 i ,為了數量的平衡還要減少 j ,幸運的是 \(j=(m+n+1)/2-i\),i 增大,j 自然會減少。
-
\(A[i-1]>B[j]\),並且為了不越界,要保證 \(i!=0,j!=n\),此時和上邊的情況相反,我們要減少 i ,增大 j 。
上邊兩種情況,我們把邊界都排除了,需要單獨討論。
-
當 i=0, 或者 j=0,也就是切在了最前邊。此時左半部分當 j = 0 時,最大的值就是 \(A[i-1]\) ;當 i=0 時 最大的值就是 \(B[j-1]\)。右半部分最小值和之前一樣。
-
當 i = m 或者 j = n,也就是切在了最后邊。此時左半部分最大值和之前一樣。右半部分當 \(j=n\) 時,最小值就是 \(A[i]\);當 i = m 時,最小值就是\(B[j]\)。
所有的思路都理清了,最后一個問題,增加 i 的方式。當然用二分了。初始化 i 為中間的值,然后減半找中間的,減半找中間的,減半找中間的直到答案。
源代碼
class Solution {
public double findMedianSortedArrays(int[] A, int[] B) {
int m = A.length;
int n = B.length;
if (m > n) {
return findMedianSortedArrays(B,A); // 保證 m <= n
}
int iMin = 0, iMax = m;
while (iMin <= iMax) {
int i = (iMin + iMax) / 2;
int j = (m + n + 1) / 2 - i;
if (j != 0 && i != m && B[j-1] > A[i]){ // i 需要增大
iMin = i + 1;
}
else if (i != 0 && j != n && A[i-1] > B[j]) { // i 需要減小
iMax = i - 1;
}
else { // 達到要求,並且將邊界條件列出來單獨考慮
int maxLeft = 0;
if (i == 0) { maxLeft = B[j-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;
}
}
總結
- 時間復雜度
我們對較短的數組進行了二分查找,所以時間復雜度是 \(O(log(min(m,n)))\)。
- 空間復雜度
只有一些固定的變量,和數組長度無關,所以空間復雜度是 \(O(1)\)。