1. 程式人生 > >資料結構演算法題/兩個有序陣列的中位數

資料結構演算法題/兩個有序陣列的中位數

有三種方法,時間複雜度分別是O(m+n) ,O(k),O(log(m+n))

注意點:
判斷合併後的陣列的元素個數是奇數還是偶數
如果是奇數取中間值;如果是偶數取中間2個數的平均值。

兩種求中位數的方法:
(1)方法1,判斷奇數個還是偶數個

if (lengthall % 2 == 0){
    result = ( all[lengthall/2-1] + all[lengthall/2] )*1.0/2;
}else{
    result = all[lengthall/2];
}
(2)方法2,不需要判斷奇數個還是偶數個
        int l = (lengthall+1)/2-1;
        int r = (lengthall+2)/2-1;
        //上面減1的原因是座標從0開始的
        result = (all[l] + all[r])*1.0/2;
        //這種求中位數的方法不需要判斷是奇數還是偶數個

思考:

中位數相當於合併之後的top(n/2),如果是其他top數也是可以的。所以如果是一個無序陣列的top k就只用小頂堆。如果是兩個有序數組合並之後的top k就可以用如下的方法。

(1)O(m+n)合併兩有序陣列成一個新有序陣列,再按中間位置取值。

兩個有序數組合併為一個有序陣列的時間複雜度O(m+n)。請參考https://blog.csdn.net/fkyyly/article/details/83145276

/**1
 * O(m+n)
 * 合併兩有序陣列成一個新有序陣列,再按中間位置取值
 * @param nums1
 * @param nums2
 * @return
 */
public static double findMedianSortedArrays1(int[] nums1, int[] nums2) {
    int length1 = nums1.length;
    int length2 = nums2.length;
    int lengthall = length1 + length2;
    int[] all = new int[length1+length2];
    int i=0,j=0,k=0;
    while (i<length1 && j <length2){
        if(nums1[i]<nums2[j]){
            all[k]=nums1[i];
            i++;
            k++;
        }else{
            all[k] = nums2[j];
            j++;
            k++;
        }
    }
    while (i<length1){
        all[k]=nums1[i];
        i++;
        k++;
    }
    while (j<length2){
        all[k] = nums2[j];
        j++;
        k++;
    }
    double result;
    if (lengthall % 2 == 0){
        result = ( all[lengthall/2-1] + all[lengthall/2] )*1.0/2;
    }else{
        result = all[lengthall/2];
    }
    return result;
}

(2)O(k)兩個指標分別從兩陣列開頭指,比較兩指標處的數,誰小誰往後移,直到兩指標共移動k次,k為中間位置

/**2
 * O(k)
 * 兩個指標分別從兩陣列開頭指,比較兩指標處的數,誰小誰往後移,
 * 直到兩指標共移動k次,k為中間位置
 * 注意總數有奇數個還是偶數個計算中位數的方法
 * @param nums1
 * @param nums2
 * @return
 */
public static double findMedianSortedArrays2(int[] nums1, int[] nums2) {
    int length1 = nums1.length;
    int length2 = nums2.length;
    int lengthall = length1 + length2;

    int inx1 = 0;
    int inx2 = 0;
    int x = -1;
    int y = -1;

    while (inx1 + inx2 < lengthall){
        if (inx1 < length1) {
            while (inx2 == length2 ||  nums1[inx1] <= nums2[inx2] ) {
            //nums1到頭了或者nums1此時的元素小於nums2此時的元素,這兩種情況下從nums1取值(也就是nums1和nums2小的移動)
                inx1++;
                //下面是總長度是奇數還是偶數對於中位數的計算
                if (inx1 + inx2 == (lengthall + 1) / 2) {
                    x = nums1[inx1 - 1];
                }
                if (inx1 + inx2 == (lengthall + 2) / 2) {
                    y = nums1[inx1 - 1];
                    return (x + y) * 1.0 / 2;
                }
                if (inx1 == length1){
                    break;
                }
            }
        }
        if (inx2 < length2){
            while ( inx1 == length1 || nums2[inx2] <= nums1[inx1] ) {
                inx2++;
                if (inx1 + inx2 == (lengthall + 1)/2){
                    x = nums2[inx2-1];
                }
                if (inx1 + inx2 == (lengthall + 2)/2){
                    y = nums2[inx2-1];
                    return (x+y)*1.0/2;
                }
                if (inx2 == length2){
                    break;
                }
            }
        }
    }
    return -1;
}

(3)O(log(m+n))兩個陣列分別採用二分法查詢

思想:上面的是陣列num1[],下面是陣列num2[]。

使用遞迴的思想分別對num1和num2求中位數,因為是有序的,所以直接取n/2處的值即可。

(1)如果num1的中位數=num2的中位數,那麼就是最終的結果。

(2)如果num1的中位數<num2的中位數,那麼下次就在[B,C]和[D,E]兩個新陣列直接找中位數。

(3)如果num1的中位數>num2的中位數,那麼下次就在[A,B]和[E,F]兩個新陣列直接找中位數。

(4)重複這個步驟,直到新陣列的長度為2

如果陣列a的中位數小於陣列b的中位數,那麼整體的中位數只可能出現在a的右區間加上b的左區間之中; 
如果陣列a的中位數大於等於陣列b的中位數,那麼整體的中位數只可能出現在a的左區間加上b的右區間之中。 

關鍵就是利用分治的思想逐漸縮小a的區間和b的區間來找到中位數。

首先假設陣列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[0]到A[k/2-1]的元素都在A和B合併之後的前k小的元素中。換句話說,A[k/2-1]不可能大於兩數組合並之後的第k小值,所以我們可以將其拋棄。


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

當A[k/2-1]=B[k/2-1]時,我們已經找到了第k小的數,也即這個相等的元素,我們將其記為m。由於在A和B中分別有k/2-1個元素小於m,所以m即是第k小的數。(這裡可能有人會有疑問,如果k為奇數,則m不是中位數。這裡是進行了理想化考慮,在實際程式碼中略有不同,是先求k/2,然後利用k-k/2獲得另一個數。)

通過上面的分析,我們即可以採用遞迴的方式實現尋找第k小的數。此外我們還需要考慮幾個邊界條件:

如果A或者B為空,則直接返回B[k-1]或者A[k-1];
如果k為1,我們只需要返回A[0]和B[0]中的較小值;
如果A[k/2-1]=B[k/2-1],返回其中一個;

https://blog.csdn.net/hjhjhx26364/article/details/80251675 
https://blog.csdn.net/darminz/article/details/77500474