leetcode 4.median of Two Sorted Arrays

@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]

如果我們可以確保:

  1. len(left_part) == len(right_part)
  2. max(left_part) <= min(right_part)

然後我們把A+B中所有的元素切分成兩個相同的長度的子集合, 並且左集合所有的元素不大於右集合的所有元素,其次我們可以得到:
中位數 = (max(left_part) + min(right_part)) / 2

要確保上面的兩種情況,我們只需確保:

  1. i + j == m - i + n - j (or: m - i + n - j + 1)
    如果n >= m,我們需要取i = 0 ~ m, j = (m + n + 1) / 2 - i
  2. 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

在循環中,我們將會遇到以下三種情形:

  1. (j == 0 or i == m or B[j -1] <= A[i]) and
    (i == 0 or j == n or A[i - 1] <= B[j])
    這個情況表明我們找到了合適的i
  2. i < m and B[j - 1] > A[i]
    表明i的值太小,我們應該增加i的值,通過修改查找範圍的下限
  3. 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],那麼可以得到以下三種情況:

  1. a[k / 2 - 1] < b [k / 2 - 1],這個表明,a數組的k / 2個元素小於ab合併之後的k小元素,爲啥呢,因爲a[k / 2 - 1] < b k / 2 - 1
  2. a[k / 2 - 1] > b [k / 2 - 1]同理b數組的k / 2個元素小於ab合併之後的k小元素,所以答案不可能出現在這。
  3. 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);
            }
        }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章