【LeetCode Hot 100】4. 尋找兩個正序陣列的中位數

随机生成一个id發表於2024-09-19

題目描述

要求出兩個陣列的中位數,第一想法當然是將這兩個陣列進行歸併排序,然後直接得到排序後長陣列的中位數。由於本題的兩個陣列都是排序後的陣列,因此省去了排序的步驟。這種方法的時間複雜度為\(O(m+n)\),空間複雜度由於要儲存排序後的長陣列,所以也是\(O(m+n)\)

有沒有相對更簡單的方法呢?由於我們只需要求出中位數,並不需要得到合併後的陣列,所以我們可以從左往右逐個元素遍歷並計數,只要我們達到了中位數所需要的下標,就可以停下來並計算中位數了。需要注意的是,如果總長度為奇數,中位數應該是中間兩個元素的算術平均,因此除了當前元素之外,還需要維護前一個元素。顯然,這種方法的時間複雜度為\(O(\frac{m+n}{2})\),空間複雜度則相對第一種方法最佳化為了常數級。

// C++
class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int m = nums1.size(), n = nums2.size();
        int len = m + n;
        int prev = -1, curr = -1;
        int i = 0, j = 0;
        for (int cnt = 0; cnt <= len / 2; cnt++) {
            prev = curr;
            if (i < m && j < n) {
                if (nums1[i] < nums2[j]) {
                    curr = nums1[i++];
                } else {
                    curr = nums2[j++];
                }
                continue;
            }
            if (i < m) {
                curr = nums1[i++];
            }
            if (j < n) {
                curr = nums2[j++];
            }
        }
        if (len % 2) {
            return curr;
        }
        return (prev + curr) / 2.0;
    }
};

但是,題目描述中要求解法的時間複雜度為對數級別,看到這個要求,就要想到與二分有關。

要找到兩個陣列的中位數,就是要找到第\(\frac{m+n}{2}\)小(和第\(\frac{m+n}{2}+1\)小)的元素。這個方法則給出了更通用的方法,用於求出第\(k\)小的元素。

要找到兩個升序陣列總共的第\(k\)小的元素,我們可以先比較兩個陣列下標為k/2 - 1的元素,假設a[k/2 - 1] < b[k/2 - 1],那麼a陣列的前面幾個元素只會更小,我們即使假設b陣列的前面幾個元素都比a[k/2 - 1]小,此時該元素也“僅僅”是總共第\(\frac{k}{2}-1\)小的元素,也就是說它最大也只能達到這個程度,而絕不可能是第\(k\)小的元素,那麼我們就可以放心地把a[0..k/2-1]這個子陣列全部“丟棄”不看,這樣我們一次性排除了\(\frac{k}{2}\)個元素。隨後我們可以繼續用這個方法排除下去,直到k=1

當然,有一些特殊情況需要考慮:

  • a[k/2 - 1]b[k/2 - 1]越界,我們需要選取的是陣列的最後一個元素。
  • 其中一個陣列的元素全部被排除,可以直接返回另一個陣列第\(k\)小的元素。
  • \(k=1\),只要返回第一個元素的較小值即可。
// C++
// https://leetcode.cn/problems/median-of-two-sorted-arrays/solutions/258842/xun-zhao-liang-ge-you-xu-shu-zu-de-zhong-wei-s-114/
class Solution {
public:
    int getKthElement(const vector<int>& nums1, const vector<int>& nums2, int k) {
        /* 主要思路:要找到第 k (k>1) 小的元素,那麼就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 進行比較
         * 這裡的 "/" 表示整除
         * nums1 中小於等於 pivot1 的元素有 nums1[0 .. k/2-2] 共計 k/2-1 個
         * nums2 中小於等於 pivot2 的元素有 nums2[0 .. k/2-2] 共計 k/2-1 個
         * 取 pivot = min(pivot1, pivot2),兩個陣列中小於等於 pivot 的元素共計不會超過 (k/2-1) + (k/2-1) <= k-2 個
         * 這樣 pivot 本身最大也只能是第 k-1 小的元素
         * 如果 pivot = pivot1,那麼 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把這些元素全部 "刪除",剩下的作為新的 nums1 陣列
         * 如果 pivot = pivot2,那麼 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把這些元素全部 "刪除",剩下的作為新的 nums2 陣列
         * 由於我們 "刪除" 了一些元素(這些元素都比第 k 小的元素要小),因此需要修改 k 的值,減去刪除的數的個數
         */

        int m = nums1.size();
        int n = nums2.size();
        int index1 = 0, index2 = 0;

        while (true) {
            // 邊界情況
            if (index1 == m) {
                return nums2[index2 + k - 1];
            }
            if (index2 == n) {
                return nums1[index1 + k - 1];
            }
            if (k == 1) {
                return min(nums1[index1], nums2[index2]);
            }

            // 正常情況
            int newIndex1 = min(index1 + k / 2 - 1, m - 1);
            int newIndex2 = min(index2 + k / 2 - 1, n - 1);
            int pivot1 = nums1[newIndex1];
            int pivot2 = nums2[newIndex2];
            if (pivot1 <= pivot2) {
                k -= newIndex1 - index1 + 1;
                index1 = newIndex1 + 1;
            }
            else {
                k -= newIndex2 - index2 + 1;
                index2 = newIndex2 + 1;
            }
        }
    }

    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int totalLength = nums1.size() + nums2.size();
        if (totalLength % 2 == 1) {
            return getKthElement(nums1, nums2, (totalLength + 1) / 2);
        }
        else {
            return (getKthElement(nums1, nums2, totalLength / 2) + getKthElement(nums1, nums2, totalLength / 2 + 1)) / 2.0;
        }
    }
};

相關文章