@author stormma
@date 2017/11/05
生命不息,奮鬥不止!
** 題目
There are two sorted arrays nums1 and nums2 of size m and n respectively.
Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)).
Example 1:
nums1 = [1, 3]
nums2 = [2]
The median is 2.0
Example 2:
nums1 = [1, 2]
nums2 = [3, 4]
The median is (2 + 3)/2 = 2.5
思路1(翻譯自Leetcode大神分析)
要解決這個問題,我們需要理解什麼是中位數,在統計學中,中位數用於:
切分一個集合爲兩個長度相等的子集合,並且第一個子集合總是小於第二個子集合。
如果我們理解了這個概念,那麼我們就很接近答案了(在我看來並不是)。首先,讓我們用一個隨機的位置i切分數組A爲兩個子集合。
left_A | right_A
A[0], A[1], ..., A[i - 1] |A[i], A[i + 1], ...., A[m - 1]
假定A集合有m個元素,那我們有m + 1種切分方式 (i = 0 ~ m),並且我們知道:
len(left_A) = i, len(right_A) = m - i
注: 當i = 0, left_A是空,當i = m right_A是空
同理,使用隨機位置j切分B集合爲兩個子集合:
left_B | right_B
b[0], B[1], ..., B[j - 1] | B[j], B[j + 1], ..., B[n - 1]
接下來,讓我們把left_A和left_B放入一個集合,把right_A和right_b放入另外一個,並取名第一個集合爲左半部分,另外一個爲右半部分。
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]
如果我們可以確保:
- len(left_part) == len(right_part)
- max(left_part) <= min(right_part)
然後我們把A+B中所有的元素切分成兩個相同的長度的子集合, 並且左集合所有的元素不大於右集合的所有元素,其次我們可以得到:
中位數 = (max(left_part) + min(right_part)) / 2
要確保上面的兩種情況,我們只需確保:
- i + j == m - i + n - j (or: m - i + n - j + 1)
如果n >= m,我們需要取i = 0 ~ m, j = (m + n + 1) / 2 - i- B[j - 1] <= A[i] 並且 A[i - 1] <= B[j]
注:
1. 爲了簡便起見,我們現在假設A[i - 1], A[i], B[j - 1], B[j]都是有效的,後面我們再處理邊界條件
2. 爲什麼n >= m ? 因爲我們必須確保j是非負的,0 <= i <= m 並且j = (m + n + 1) / 2 - i, 如果n < m, 那麼j必定是負數,這並不能讓我們得到正確的答案。
所以,總之,我們只需做:
在[0, m]範圍尋找一個i的值滿足
B[j - 1] <= A[i] 並且 A[i - 1] <= B[j], j = (m + n + 1) / 2 - i
我們可以用二分查找法來模擬這個過程:
1. 初始化 iMin = 0, iMax = m, 然後在[iMin, iMax]這個範圍查找
2. 設 i = (iMin + iMax) / 2,j = (m + n + 1) / 2 -i
3. 現在,我們得到abs (len(left_part) - len(right_part)) <= 1(此處是我修改的,作者原表達式爲len(left_part) == len(right_part))。然後,我們會遇到以下三種情況:
- B[j - 1] <= A[i] 並且 A[i - 1] <= B[j],這種情況表明我們得到了我們想要的答案
- B[j - 1] > A[i]這種情況表明,我們A[i]的值過小了,我們應該調整i的值得到B[j - 1] <= A[i],我們怎麼調整i?
- 增加i?,是的,這種方式很正確,因爲增加i,j肯定是減小的,所以B[j - 1]減小, A[i]增加,所以肯定能找到我們需要的B[j - 1] <= A[i]
- 減少i? 錯啦老鐵,i 減少,j增加,所以B[j - 1]繼續增加,A[i]繼續減少,那麼一輩子也看不到B[j - 1] <= A[i]。
所以我們必須增加i,我們可以調整範圍爲[i + 1, iMax],所以設置iMin = i + 1並重復2.
- A[i - 1] >= B[j]: 這個情況表明A[i - 1]過大,我們應該減小i的值,保證A[i - 1] <= B[j],對此,我們可以調整範圍爲[iMin, i - 1],只需設置iMax = i - 1,並重復2
上面的步驟,當我們知道了合適的i的值,那麼當:
m + n 是偶數的時候,我們應該返回 max(A[i - 1], B[j - 1)
m + n 是奇數的時候,我們應該返回 (max(A[i - 1], B[j - 1]) + min(A[i], B[j])) / 2
現在我們來考慮邊界條件i = 0, i = m, j = 0, j = n,當A[i - 1], B[j - 1], A[i], B[j]不存在時,實際上,這種情況要比你想象得更簡單
。
我們應該怎麼做來保證max(left_part) <= min(right_part)。如果i 和 j 都不是邊界條件時候,那麼表明A[i - 1], A[i], B[j - 1], B[j]都存在,然後我們必須檢查B[j - 1] <= A[i]並且A[i - 1] <= B[j]。但是如果A[i - 1], A[i], B[j - 1], B[j]不存在,我們不需要檢查這兩個情況或其中任意一個。舉個例子,i = 0, A[i - 1]不存在,我們不需要檢查A[i - 1] <= B[j],所以我們應該做以下事情:
在[0, m]查找一個i的值,滿足
(j == 0 or i == m or B[j - 1] <= A[i]) 並且
(i = 0 or j == n or A[i - 1] <= B[j]),j = (m + n + 1) / 2 - i
在循環中,我們將會遇到以下三種情形:
- (j == 0 or i == m or B[j -1] <= A[i]) and
(i == 0 or j == n or A[i - 1] <= B[j])
這個情況表明我們找到了合適的i- i < m and B[j - 1] > A[i]
表明i的值太小,我們應該增加i的值,通過修改查找範圍的下限- j < n and A[i - 1] > B[j]
表明i的值過大,我們應該減小i,通過修改查找範圍的上限
補充:
i < m ⇒ j > 0
m <= n, i < m => j = (m + n + 1) / 2 -i > (m + n + 1) / 2 -m >=(2m + 1) / 2 - m >= 0
i > 0 => j < n
m <= n, i > 0 => j = (m + n + 1) / 2 - i < (m + n + 1) / 2 < (2n + 1) / 2 <= n
代碼實現
時間複雜度 log(min(m, n))
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
if (nums1.length > nums2.length) {
int[] temp = nums1; nums1 = nums2; nums2 = temp;
}
int m = nums1.length, n = nums2.length;
int iMin = 0, iMax = m, halfLen = (m + n + 1) >> 1;
while (iMin <= iMax) {
int i = (iMin + iMax) >> 1;
int j = halfLen - i;
if (i > iMin && nums1[i - 1] > nums2[j]) { // i is too big
iMax = i - 1;
} else if (i < iMax && nums2[j - 1] > nums1[i]) { // i is too small
iMin = i + 1;
} else { // find correct i
int maxLeft = 0;
if (i == 0) {
maxLeft = nums2[j - 1];
} else if (j == 0) {
maxLeft = nums1[i - 1];
} else {
maxLeft = Math.max(nums1[i - 1], nums2[j - 1]);
}
if ((m + n) % 2 == 1) {
return maxLeft;
}
int minRight = 0;
if (i == m) {
minRight = nums2[j];
} else if (j == n) {
minRight = nums1[i];
} else {
minRight = Math.min(nums1[i], nums2[j]);
}
return (minRight + maxLeft) / 2.0;
}
}
return 0.0;
}
思路2(遞歸方式)
其實問題可以轉換爲在a,b數組中尋找第k大數,那麼我們可以先比較a[k / 2 - 1]和b[k / 2 - 1],那麼可以得到以下三種情況:
- a[k / 2 - 1] < b [k / 2 - 1],這個表明,a數組的k / 2個元素小於ab合併之後的k小元素,爲啥呢,因爲a[k / 2 - 1] < b k / 2 - 1
- a[k / 2 - 1] > b [k / 2 - 1]同理b數組的k / 2個元素小於ab合併之後的k小元素,所以答案不可能出現在這。
- a[k / 2 - 1] = b [k / 2 - 1],此時的這個數就是第k小元素
那麼我們來看一下邊界條件
如果A或者B爲空,則直接返回B[k - 1]或者A[k - 1];
如果k爲1,我們只需要返回A[0]和B[0]中的較小值;
如果A[k / 2 - 1] = B[k / 2 - 1],返回其中一個;
代碼實現
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length, n = nums2.length;
// 1 2 3 4
if ((m + n) % 2 == 0) {
return (findKth(nums1, 0, nums2, 0, (m + n) / 2) + findKth(nums1, 0, nums2, 0,
(m + n) / 2 + 1)) / 2.0;
} else {
return findKth(nums1, 0, nums2, 0, (m + n + 1) / 2);
}
}
/**
* 尋找合併數組中第k大元素
* @param a
* @param aStart
* @param b
* @param bStart
* @param k
* @return
*/
private int findKth(int[] a, int aStart, int[] b, int bStart, int k) {
if (aStart >= a.length) {
return b[bStart + k - 1];
}
if (bStart >= b.length) {
return a[aStart + k - 1];
}
if (k == 1) {
return Math.min(a[aStart], b[bStart]);
}
int aMid = Integer.MAX_VALUE, bMid = Integer.MAX_VALUE;
if ((aStart + k / 2 - 1) < a.length) {
aMid = a[aStart + k / 2 - 1];
}
if ((bStart + k / 2 - 1 ) < b.length) {
bMid = b[bStart + k / 2 - 1];
}
// a前k / 2個數大於b 前k / 2個元素,
if (aMid > bMid) {
return findKth(a, aStart, b, bStart + k / 2, k - k / 2);
} else {
return findKth(a, aStart + k / 2, b, bStart, k - k / 2);
}
}