題目
兩個排序陣列的中位數
給定兩個大小為 m 和 n 的有序陣列 nums1 和 nums2 。
請找出這兩個有序陣列的中位數。要求演算法的時間複雜度為 O(log (m+n)) 。
示例 1:
nums1 = [1, 3]
nums2 = [2]
中位數是 2.0
示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
中位數是 (2 + 3)/2 = 2.5
複製程式碼
思路:
- 中位數下標 (假設陣列為arr,長度為len,下標範圍[0 ,len - 1])
- 如果len % 2 = 1 (為奇數),那麼中位數的值為arr[mindIndex = len /2] 。
- 如果len % 2 - 0 (為偶數),那麼中位數的值為(arr[len/2]+arr[len/2 - 1])/2.0的值的和的平均值。
- 思路一:最簡單的思路把兩個有序陣列合併成一個有序的陣列,再通過規則1.獲取中位數的值。優化:結合題目要求,只需要獲取中位數的值,並不需要真的要把兩個陣列排序成一個有序陣列。那麼通過兩陣列間隔讀取,下標後移,直到累加到位置為為len/2即可獲取到結果。這裡如何更快地把找到len/2的位置是效率的關鍵。
- 思路二:假定陣列A、B、長度分別為Alen、Blen。那麼排序後的新陣列為C、Clen。Clen = (Alen + Blen + 1)/ 2。因為A和B都是有序的,如果C是由A和B的一部分元素組成的。那麼可以想象,C必然是A左邊一定長度為ALefPairLen和B的左邊一定長度為BLeftPairLen的兩部分組成.ALefPairLen和BLeftPairLen是聯動關係,ALefPairLen + BLeftPairLen = (Alen + Blen + 1)/ 2,一增一減,一減一增,總長度是固定的。然後為了獲得中位數,只需要獲取其中一個陣列的用於合併那部分的末尾Index,或者index + 1 (index + 1 = LefPairLen) ;這就變成了一個查詢指定下標的問題了。
繼續思路二(切換到程式中的變數,便於理解)
-
定義: 假定,A陣列的用於合併的長度為i,,那麼最後一個元素的下標為i-1,i為用於合併的最後一個元素的下一個元素的下標。B的陣列的長度j = (Alen + Blen + 1)/ 2 - i,那麼B陣列的最後一個元素的下標為j-1,j為用於合併的最後一個元素的下一個元素的下標。
-
結束條件: 要獲取合法的i值,需要滿足 A[i -1 ] <= B[j] && A[j] >= B[j -1]; 如果leftPairA的最後一個元素少於leftPairB在原陣列中最後一個元素的下一個元素,那麼leftPairA是符合要求的。反過來B陣列也一樣。要達到在leftPairA和leftPairB右邊的元素都比這兩部分大,不然就需要繼續調整。
-
特殊情況:程式碼註釋中記錄。
-
數的獲取: 因為取長度的時候為halfLen = (Alen + Blen + 1)/ 2。如果總長度為奇數,最後一位就是中位fmax(a[i-1],B[j-1])。如果總長度為偶數,需要取處於中間的兩個數,最後一位(fmax(a[i-1],B[j-1])為左邊的一個,右邊一個為halfLen之外的fmin(A[i],B[j]))。
double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size) {
int* A = nums1;
int* B = nums2;
int m = nums1Size;
int n = nums2Size;
if (m > n ) {
int* tmp = A;
A = B;
B = tmp;
int k = m;
m = n;
n = k;
}
int iMin = 0;
int iMax = m;
//halfLen 偶數等於實際的一半,奇數等於向上取整
int halfLen = (m + n + 1) / 2 ;
while (iMin <= iMax) {
//偶數index[i-1 , i ],奇數[i]
int i = (iMin + iMax) /2;
//長度之外的第一個元素
int j = halfLen - i;
if (i < iMax && A[i] < B[j - 1] )
{
//太小了,下限提高
iMin = iMin + 1;
}
else if ( i > iMin && A[i- 1] > B[j]) {
//太大了,上限降低
iMax = iMax - 1;
}
else
{
//滿足需求 (A[i - 1] < B[j] && A[i] > B[j - 1]) || i == 0 || j == 0 || i == m || j == n
int leftMax = 0;
//m > n
if (i == 0) {
//在A被選中的長度為0
leftMax = B[j - 1];
}else if (j == 0)
{
//在B中被選中的長度為0
leftMax = A[i - 1];
}
else
{
//i = m;代表A陣列都被選中
//j = n;代表整個B陣列都被選中
//包含 j == n || i == m 的情況
leftMax = fmax(A[i - 1], B[j - 1]);
}
if ((m + n) % 2 == 1) {
return leftMax;
}
int rightMin = 0;
if (i == m)
{
//A全部被選中。那麼總長度為偶數時,中位數為中間兩數的平均值,選中的最後一個數為leftMax,rightMin為hanlflen之外的第一個數B[j];
rightMin = B[j];
}else if (j == n)
{
//B全部被選中。那麼總長度為偶數時,中位數為中間兩數的平均值,選中的最後一個數為leftMax,rightMin為hanlflen之外的第一個數A[j];
rightMin = A[i];
}
else
{
//包含j == 0 || i == 0 的情況
rightMin = fmin(A[i], B[j]);
}
return (leftMax + rightMin) / 2.0 ;
}
}
return 0;
}
複製程式碼
//另外一種效率不錯的解法
double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size) {
int* A = nums1;
int* B = nums2;
int m = nums1Size;
int n = nums2Size;
if (m < n ) {
int* tmp = A;
A = B;
B = tmp;
int k = m;
m = n;
n = k;
}
int iMin = 0;
int iMax = m;
int halfLen = (m + n + 1) / 2 ;
while (iMin <= iMax) {
//偶數index[i-1 , i ],奇數[i]
int i = (iMin + iMax) /2;
//長度之外的第一個元素
int j = halfLen - i;
if ( A[i- 1] > B[j] && i > iMin ) {
//太大了,上限降低
iMax = iMax - 1;
}
else if (A[i] < B[j - 1] && i < iMax)
{
//太小了,下限提高
iMin = iMin + 1;
}
else
{
//滿足需求 A[i - 1] < B[j] && A[i] > B[j - 1]
int leftMax = 0;
//m > n
if (i == 0) {
//在A中無法找到符合的數值
leftMax = B[j - 1];
}else if (j == 0)
{
//在B中無法找到符合的數值
leftMax = A[i - 1];
}
else
{
//包含 j == n || i == m 的情況
leftMax = fmax(A[i - 1], B[j - 1]);
if ((m + n) % 2 == 1) {
return leftMax;
}
}
int rightMin = 0;
if (i == m)
{
rightMin = B[j];
}else if (j == n)
{
rightMin = A[i];
}
else
{
//包含j == 0 || i == 0 的情況
rightMin = fmin(A[i], B[j]);
return (leftMax + rightMin) / 2.0 ;
}
}
return 0;
}
複製程式碼
小結:這道理題要處理的細節比較多。切割長度halfLen = (m + n + 1) / 2,最後一位是哪一位,是剛好是中位數呢,還是中位數左移一位,還是右移一位呢。這個會影響到後面的中位數下標取值。還要搞懂每個臨界條件代表的意義。總的來說如果每一句程式碼都是必須的,那麼必然要做到對程式碼的含義瞭然於心,才能保證程式的正確性。對的思路+對的編碼 = 對的答案。
待完善L('ω')┘三└('ω')」....