[LeetCode] 4. Median of Two Sorted Arrays
//令中位數為M //runt的次數,取得n+1 ,偶數M=(n + n+1)/2,奇數M=n; int maxRun = (1 + nums1.Length + nums2.Length) / 2 + 1; //奇數中位數此值為false //偶數中位數此值為true 要在耶後一個元素相除 bool isNeedGetPreElement = (1 + nums1.Length + nums2.Length) % 2 != 0; int element1 = -1, element2 = -1; int temp = maxRun; if (maxRun == 1) { return 0; } if (nums1.Length == 0) { return isNeedGetPreElement ? (nums2[maxRun - 1] + nums2[maxRun - 2]) / 2.0 : nums2[maxRun - 2] / 1.0; } if (nums2.Length == 0) { return isNeedGetPreElement ? (nums1[maxRun - 1] + nums1[maxRun - 2]) / 2.0 : nums1[maxRun - 2] / 1.0; } int nAdd1 = 0; int n = 0; for (int run = 1, nums1Index = 0, nums2Index = 0; run <= maxRun; run++) { int tempVal = 0; //nums1所有元素比較完畢,直接計算 if (nums1Index == nums1.Length) { nAdd1 = nums2[nums2Index + temp - 1]; n = temp - 2 >= 0 ? nums2[nums2Index+temp - 2] : n; break; } if (nums2Index == nums2.Length) { nAdd1 = nums1[nums1Index + temp - 1]; n = temp - 2 >= 0 ? nums1[nums1Index+temp - 2] : n; break; } element1 = nums1[nums1Index]; element2 = nums2[nums2Index]; //每一輪比較 取出最小的 直到取到 maxElementIndex if (element1 >= element2) { tempVal = element2; nums2Index++; } else if (element1 < element2) { tempVal = element1; nums1Index++; } if (temp == 2) n = tempVal; if (temp == 1) nAdd1 = tempVal; temp--; } return isNeedGetPreElement ? (n + nAdd1) / 2.0 : n;
悲慘的效率
討論內有一篇 Share my O(log(min(m,n)) solution with explanation
為了解決這個問題,我們需要了解"什麼是中位數",統計上,中位數是用於分割一個集合為兩個等長的子集,其中一個子集總是大於另一個。如果我們了解中位數的分割方法,我們離答案非常這了。
首先,我們在位置i 分割A為兩部份。
因為A有m個元素,所以有m+1種分割方法(i=0~m)。且我們知道:len(left_A)=i,len(right_A) = m-i。注意:當i = 0,left_A是空的,且當i = m,right_A是空的。
同樣的方法,把B在隨機位置j分成兩部份。
把left_A,left_B放在同一個集合內,right_A,right_B放在另一個集合,我們命名為left_part,right_part
如果我們可以確認
我們把所有元素分成等長的{A,B},且其中一個總是大於另一個,然中位數 = (max(left_part) + min(right_part)) /2。
為了確保這兩個條件,我們必需保證。
ps.1 為了簡化,我假設 A[i-1],B[j-1],A[i],B[j]總是合法,即使i=0/i=m/j=0/j=n.之後我會談論怎麼處理這些邊界值。
ps2 為什麼n>=m?因為當0<=i<=m and j = (m+n+1)/2 -i。我必需確保j是非負值。如果n < m ,j可能是負值,會產生錯誤結果。
所以,所有我們應該做的是:
然後我們可以依下面步驟做二元搜尋:
當i被找到,中位數是:
現在我們考慮邊界值 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],B[j-1],A[i],B[j]都存在),然後我們必需檢查B[j-1] <= A[i] and A[i-1] <= B[j]
但如果A[i-1],B[j-1],A[i],B[j]有不存在的,我們就不需要確認一個、或兩個條件,例如,如果i =0,A[i-1]不存在,我們就不需要檢查A[i-1] <= B[j]
所以我們需要做的是:
在迴圈中我們只會遇到三種情況:
感謝@Quentin.chen,他指出了i < m ==> j > 0
and i > 0 ==> j < n,因為:
所以在狀況b和c,我們不需要檢查是否j > 0或j < n
下面是通過的代碼:
C#
int m = nums1.Length, n = nums2.Length; //假設n >= m 反過來就調換 if(m > n) { int[] temp = nums1; nums1 = nums2; nums2 = temp; m = nums1.Length; n = nums2.Length; } if (n == 0) throw new Exception(); int imin =0, imax =m, half_len = (m + n + 1) / 2; int max_of_left, min_of_right; while (imin <= imax) { int i = (imin + imax) / 2; int j = half_len - i; if (i < m && nums2[j - 1] > nums1[i]) //i is too small, must increase it imin = i + 1; else if (i > 0 && nums1[i - 1] > nums2[j]) // i is too big, must decrease it imax = i - 1; else { //i is perfect if (i == 0) max_of_left = nums2[j - 1]; else if (j == 0) max_of_left = nums1[i - 1]; else max_of_left = new int[] { nums1[i - 1], nums2[j - 1] }.Max(); if ((m + n) % 2 == 1) return (double)max_of_left; if (i == m) min_of_right = nums2[j]; else if (j == n) min_of_right = nums1[i]; else min_of_right = new int[] { nums1[i ], nums2[j ] }.Min(); return (max_of_left + min_of_right) / 2.0; } } return -1.0;