尋找兩個有序數組的中位數
Median of Two Sorted Arrays
題目描述
解法: 遞歸法
爲了解決這個問題,我們需要理解 “中位數的作用是什麼”。在統計中,中位數被用來:
將一個集合劃分爲兩個長度相等的子集,其中一個子集中的元素總是大於另一個子集中的元素。
如果理解了中位數的劃分作用,我們就很接近答案了。
首當其衝的來講,讓我們在任一位置 將 劃分成兩個部分:
left_A | right_A
A[0], A[1], ..., A[i-1] | A[i], A[i+1], ..., A[m-1]
由於 中有 個元素, 所以我們有 種劃分的方法()。
因此我們可以得知:
當 的時候,爲空集,而當的時候,爲空集。
採用同樣的方式,我們在任一位置 將 劃分成兩個部分:
left_B | right_B
B[0], B[1], ..., B[j-1] | B[j], B[j+1], ..., B[n-1]
將 和 放入一個集合,並將 和 放入另一個集合。 再把這兩個新的集合分別命名爲 和 :
left_part | right_part
A[0], A[1], ..., A[i-1] | A[i], A[i+1], ..., A[m-1]
B[0], B[1], ..., B[j-1] | B[j], B[j+1], ..., B[n-1]
那麼當我們可以確認一個情況的時候:
那麼,我們已經將 中的所有元素劃分爲相同長度的兩個部分,且其中一部分中的元素總是大於另一部分中的元素。那麼:
要確保這兩個條件,我們只需要保證:
-
或者
-
以及
-
爲了簡化分析,我假設 總是存在,哪怕出現 ,,,或是 這樣的臨界條件。我將在最後討論如何處理這些臨界值。
-
爲什麼 由於$0 \leq i \leq m0≤i≤m $ 且 ,我必須確保 不是負數。如果 那麼 將可能是負數,而這會造成錯誤的答案。
因此我們接下來要處理的事情就是:
在 中搜索並找到目標對象 ,以達到:
且 , 其中
接着,我們可以按照以下步驟來進行二叉樹搜索:
-
設 ,, 然後開始在 中進行搜索。
-
-
現在我們有 。 而且我們只會遇到三種情況:
- :這意味着我們找到了目標對象 ii,所以可以停止搜索。
- :這意味着 太小,我們必須調整 以使 。
我們可以增大 嗎?
是的,因爲當 被增大的時候, 就會被減小。
因此 會減小,而 會增大,那麼 \text{B}[j-1] \leq \text{A}[i]B[j−1]≤A[i] 就可能被滿足。
我們可以減小 嗎?
不行,因爲當 被減小的時候, 就會被增大。
因此 會增大,而 會減小,那麼 就可能不滿足。
所以我們必須增大 。也就是說,我們必須將搜索範圍調整爲 。因此,設 ,並轉到步驟 2。 - :
這意味着 太大,我們必須減小 以使 。
也就是說,我們必須將搜索範圍調整爲 。
因此,設 ,並轉到步驟 2。
當找到目標對象 時,中位數爲:
-
, 當 爲奇數時
-
, 當 爲偶數時
現在,讓我們來考慮這些臨界值 ,此時$ \text{A}[i-1],\text{B}[j-1],\text{A}[i],\text{B}[j]$ 可能不存在。其實這種情況比你想象的要容易得多。
我們需要做的是確保 。 因此,如果 ii 和 jj 不是臨界值(這意味着 全部存在), 那麼我們必須同時檢查 以及 是否成立。
但是如果中部分不存在,那麼我們只需要檢查這兩個條件中的一個(或不需要檢查)。
舉個例子,如果 ,那麼 不存在,我們就不需要檢查 是否成立。
所以,我們需要做的是:
在 [0,m][0,m] 中搜索並找到目標對象 ii,以使:
- 或是
- , 其中
在循環搜索中,我們只會遇到三種情況:
- 或是 ,這意味着 是完美的,我們可以停止搜索。
- 這意味着 太小,我們必須增大它。
- 這意味着 太大,我們必須減小它。
以及 始終成立,這是因爲:
所以,在情況 2 和 3中,我們不需要檢查 或是 是否成立。
複雜度分析
- 時間複雜度:,
首先,查找的區間是 。
而該區間的長度在每次循環之後都會減少爲原來的一半。
所以,我們只需要執行 次循環。由於我們在每次循環中進行常量次數的操作,所以時間複雜度爲 。
由於 ,所以時間複雜度是 。 - 空間複雜度:,
我們只需要恆定的內存來存儲 99 個局部變量, 所以空間複雜度爲 。
C++代碼
class Solution
{
public:
double findMedianSortedArrays(vector<int> &nums1, vector<int> &nums2)
{
int nums1Size = int(nums1.size());
int nums2Size = int(nums2.size());
//確保數組1是較短的數組
if (nums1Size > nums2Size)
{
return findMedianSortedArrays(nums2, nums1);
}
// Ci 爲第i個數組的割,比如C1爲2時表示第1個數組只有2個元素。lMaxi爲第i個數組割後的左元素。rMini爲第i個數組割後的右元素。
int lMax1, lMax2, rMin1, rMin2, c1, c2, lo = 0, hi = 2 * nums1Size; //我們目前是虛擬加了'#'所以數組1是2*n長度
while (lo <= hi)
{ //二分法
c1 = (lo + hi) / 2;
c2 = nums1Size + nums2Size - c1;
lMax1 = (c1 == 0) ? INT_MIN : nums1[(c1 - 1) / 2];
rMin1 = (c1 == 2 * nums1Size) ? INT_MAX : nums1[c1 / 2];
lMax2 = (c2 == 0) ? INT_MIN : nums2[(c2 - 1) / 2];
rMin2 = (c2 == 2 * nums2Size) ? INT_MAX : nums2[c2 / 2];
if (lMax1 > rMin2)
{
hi = c1 - 1;
}
else if (lMax2 > rMin1)
{
lo = c1 + 1;
}
else
{
break;
}
}
return (max(lMax1, lMax2) + min(rMin1, rMin2)) / 2.0;
}
};
Golang代碼
// Solution by Panda.
// 生成一個新的數組,然後判斷長度奇偶數,取中間值。
func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
nums := combine(nums1, nums2)
return medianOf(nums)
}
func combine(mis, njs []int) []int {
lenMis, i := len(mis), 0
lenNjs, j := len(njs), 0
res := make([]int, lenMis+lenNjs)
for k := 0; k < lenMis+lenNjs; k++ {
if i == lenMis ||
(i < lenMis && j < lenNjs && mis[i] > njs[j]) {
res[k] = njs[j]
j++
continue
}
if j == lenNjs ||
(i < lenMis && j < lenNjs && mis[i] <= njs[j]) {
res[k] = mis[i]
i++
}
}
return res
}
func medianOf(nums []int) float64 {
l := len(nums)
if l == 0 {
panic("切片長度爲0, 無法求解中位數.")
}
if l%2 == 0 {
return float64(nums[l/2]+nums[l/2-1]) / 2.0
}
return float64(nums[l/2])
}
Java代碼
class Solution {
public double findMedianSortedArrays(int[] A, int[] B) {
int m = A.length;
int n = B.length;
if (m > n) { // to ensure m<=n
int[] temp = A; A = B; B = temp;
int tmp = m; m = n; n = tmp;
}
int iMin = 0, iMax = m, halfLen = (m + n + 1) / 2;
while (iMin <= iMax) {
int i = (iMin + iMax) / 2;
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;
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;
}
}
成長,就是一個不動聲色的過程,一個人熬過一些苦,才能無所不能。