Leetcode(4)-兩個排序數組的中位數


 
給定兩個大小為 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

自己的思路:既然兩個數組都是有序的,我可以將兩個數組合並成一個數組,依然有序,然后根據奇偶來判斷出中位數。

double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
{
    vector<int>nums;
    int i=0,j=0;
    while(i<nums1.size() && j<nums2.size())
    {
        if(nums1[i]<nums2[j])
        {
            nums.push_back(nums1[i++]);
        }
        else
        {
            nums.push_back(nums2[j++]);
        }
    }
    if(i==nums1.size())
    {
        while(j<nums2.size())
            nums.push_back(nums2[j++]);
    }
    else if(j==nums2.size())
    {
        while(i<nums1.size())
            nums.push_back(nums1[i++]);
    }
    if(nums.size()%2)
        return nums[nums.size()/2];
    else
    {
        return (double)(nums[nums.size()/2]+nums[nums.size()/2-1])/2;
    }
}

但是這個算法很明顯的不符合題目中的算法復雜度的要求,所以整理了他人的優秀實現方法

(1)中位數,其實就是找到第k個大小的元素的特例。在單數組中實現方式簡單,關鍵是如何在兩個數組中找到第k大的元素。

難就難在要在兩個未合並的有序數組之間使用二分法,這里我們需要定義一個函數來找到第K個元素,由於兩個數組長度之和的奇偶不確定,因此需要分情況來討論,對於奇數的情況,直接找到最中間的數即可,偶數的話需要求最中間兩個數的平均值。下面重點來看如何實現找到第K個元素,首先我們需要讓數組1的長度小於或等於數組2的長度,那么我們只需判斷如果數組1的長度大於數組2的長度的話,交換兩個數組即可,然后我們要判斷小的數組是否為空,為空的話,直接在另一個數組找第K個即可。還有一種情況是當K = 1時,表示我們要找第一個元素,只要比較兩個數組的第一個元素,返回較小的那個即可。

首先假設數組A和B的元素個數都大於k/2,我們比較A[k/2-1]和B[k/2-1]兩個元素,這兩個元素分別表示A的第k/2小的元素和B的第k/2小的元素。這兩個元素比較共有三種情況:>、<和=。如果A[k/2-1]大於B[k/2-1],則A[k/2-1]小於合並之后的第k小值。

證明也很簡單,可以采用反證法。假設A[k/2-1]大於合並之后的第k小值,我們不妨假定其為第(k+1)小值。由於A[k/2-1]小於B[k/2-1],所以B[k/2-1]至少是第(k+2)小值。但實際上,在A中至多存在k/2-1個元素小於A[k/2-1],B中也至多存在k/2-1個元素小於A[k/2-1],所以小於A[k/2-1]的元素個數至多有k/2+ k/2-2,小於k,這與A[k/2-1]是第(k+1)的數矛盾。

同理當A[k / 2 - 1] > B[k / 2 -1]時存在類似的結論

當A[k / 2 - 1] = B[k / 2 -1]時,表示,在在A的k/2 -1之前已經有k/2 -1和數小於A[k / 2 -1],同理在B 之前也是一樣的,所以此時已經找到了第k小的數,即這個相等的元素。

    double findKth(vector<int> &nums1, int i, vector<int> &nums2, int j, int k)
    {
        // 首先需要讓數組1的長度小於或等於數組2的長度
        if (nums1.size() - i > nums2.size() - j) {
            return findKth(nums2, j, nums1, i, k);
        }
        // 判斷小的數組是否為空,為空的話,直接在另一個數組找第K個即可
        if (nums1.size() == i) {
            return nums2[j + k - 1];
        }
        // 當K = 1時,表示我們要找第一個元素,只要比較兩個數組的第一個元素,返回較小的那個即可
        if (k == 1) {
            return min(nums1[i], nums2[j]);
        }
        int pa = min(i + k / 2, int(nums1.size())), pb = j + k - pa + i;
        
        if (nums1[pa - 1] < nums2[pb - 1]) {
            return findKth(nums1, pa, nums2, j, k - pa + i);
        }
        else if (nums1[pa - 1] > nums2[pb - 1]) {
            return findKth(nums1, i, nums2, pb, k - pb + j);
        }
        else {
            return nums1[pa - 1];
        }
    }
     double findMedianSortedArrays(vector<int> A, vector<int> B) {
        int sizeA = A.size(), sizeB = B.size();
        if (sizeA <= 0 && sizeB <= 0) {
            return 0;
        }
        int total = sizeA + sizeB;
        if (total % 2 == 1) {
            return findKth(A, 0, B, 0, total / 2 + 1);
        }
        else {
            return (findKth(A, 0, B, 0, total / 2) + findKth(A, 0, B, 0, total / 2 + 1)) / 2;
        }
    }

這里比較難理解的點是判斷(nums1[pa - 1] < nums2[pb - 1])之后執行了return findKth(nums1, pa, nums2, j, k - pa + i);其實這個操作是因為目前nums1的分界線的值小於nums2分界線的值,那么證明nums1分界線以及前面的值都小於合並后的第k的值,也就是中位數。那么我們可以從這里開始,繼續尋找第k-(pa-i)的值,直到兩個值相等為止。

(2)分治法。

https://hk029.gitbooks.io/leetbook/%E5%88%86%E6%B2%BB/004.%20Median%20of%20Two%20Sorted%20Arrays[H]/004.%20Median%20of%20Two%20Sorted%20Arrays[H].html

    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        if(nums1.size() == 0)
            return MedofArray(nums2);
        if(nums2.size() == 0)
            return MedofArray(nums1);
        int n = nums1.size();
        int m = nums2.size();
        if(n > m)   //保證數組1一定最短
            return findMedianSortedArrays(nums2,nums1);
        int L1,L2,R1,R2,c1,c2,lo = 0, hi = 2*n;  //我們目前是虛擬加了'#'所以數組1是2*n+1長度
        while(lo <= hi)   //二分
        {
            c1 = (lo+hi)/2;  //c1是二分的結果
            c2 = m+n- c1;
            L1 = (c1 == 0)?INT_MIN:nums1[(c1-1)/2];   //map to original element
            R1 = (c1 == 2*n)?INT_MAX:nums1[c1/2];
            L2 = (c2 == 0)?INT_MIN:nums2[(c2-1)/2];
            R2 = (c2 == 2*m)?INT_MAX:nums2[c2/2];

            if(L1 > R2)
                hi = c1-1;
            else if(L2 > R1)
                lo = c1+1;
            else
                break;
        }
        return (max(L1,L2)+ min(R1,R2))/2.0;
    }
    double MedofArray(vector<int>& nums)
    {
        if(nums.size() == 0)    return -1;
        return (nums[nums.size()/2]+nums[(nums.size()-1)/2])/2.0;
    }

需要細細思考下


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM