1. 程式人生 > >求兩個排好序的陣列的中位數

求兩個排好序的陣列的中位數

The overall run time complexity should be O(log (m+n)).

這是Princeton演算法公開課的課後習題。 資源:http://www.lintcode.com/en/problem/median-of-two-sorted-arrays/

用二分法很好想,找到a[]和b[]的各自中位數比大小,aUb的中位數肯定在這兩個數之間,這樣就砍掉了一半,實現二分...

然而實施起來的細節tricky到令我髮指(見註釋)

主要有兩點,

第一,假設a比b短,那麼a在1,2對應b奇數,偶數,甚至2個,都是不同的corner case,要各自單獨小心處理,把每一個case都弄清楚,畢竟奇數個和偶數個的中位數計算方法是不一樣的,並且陣列太短的話general method可能會越界。

第二,不是每一次都各砍掉一半的!因為這可能改變aUb的奇偶性,從而導致中位數計算方法改變。安全同時比較有效的做法是,a與b每次都砍掉相同的長度,也就是a的一半。

import java.util.Arrays;
import java.lang.Math;

class Solution {
    /**
     * @param A: An integer array.
     * @param B: An integer array.
     * @return: a double whose format is *.5 or *.0
     */
    public double findMedianSortedArrays(int[] A, int[] B) {
        int m = A.length;
        int n = B.length;
        if (m <= n)
            return findMedianAB(A, m, B, n);
        return findMedianAB(B, n, A, m);
    }

    private double findMedianAB(int[] A, int m, int[] B, int n) {
        assert(m <= n);
        if (m == 0) {
            return median(B, n);
        }

        if (m == 1) {
            if (n == 1)
                return MO2(A[0], B[0]);

            if (n % 2 == 1) {
                return (MO3(B[n/2 - 1], B[n/2 +1], A[0]) + B[n/2]) / 2.0;
            }

            return MO3(B[n/2 - 1], B[n/2], A[0]);
        }

        if (m == 2) {
            if (n == 2) {
                return MO4(A[0], A[1], B[0], B[1]);
            }

            if (n % 2 == 1) {
                return MO3( B[n/2],
                        Math.max(A[0], B[n/2 - 1]),
                        Math.min(A[1], B[n/2 + 1]) );
            }

            return MO4( B[n/2], B[n/2 - 1],
                    Math.max( A[0], B[n/2 - 2] ),
                    Math.min( A[1], B[n/2 + 1] ) );
        }

        int midA = (m - 1) / 2;
        int midB = (n - 1) / 2;
        if (A[midA] <= B[midB]) { // comparing the medians is also ok but a bit slower
            return findMedianAB( Arrays.copyOfRange(A, midA, m), m - midA,
                    Arrays.copyOfRange(B, 0, n - midA), n - midA );
        }
        // the most tricky part. reduce the length of BOTH A AND B by midA, rather than by half!
        return findMedianAB( Arrays.copyOfRange(A, 0, m - midA), m - midA,
                Arrays.copyOfRange(B, midA, n), n - midA );
        // try [1,5,6], [2,3,4,7,8] to find that dropping both arrays by half is wrong.
        // drop-by-midA approach guarantees two things:
        // 1, at least we are still dropping elements safely (only that we drop a bit less elements from B)
        // 2, we do not change the being-odd-or-even for the union of sub-A and sub-B from the original one,
        // which is important because we need to compute the median of the subset in the same way
        // as we do for the original union of A and B (odd: middle; even: average of 2 middles)
    }

    private double MO2(int a, int b) {
        return (a + b) / 2.0;
    }

    private double MO3(int a, int b, int c) {
        return a + b + c
                - Math.max(a, Math.max(b, c))
                - Math.min(a, Math.min(b, c));
    }

    private double MO4(int a, int b, int c, int d) {
        int max = Math.max( a, Math.max( b, Math.max( c, d ) ) );
        int min = Math.min( a, Math.min( b, Math.min( c, d ) ) );
        return (a + b + c + d - max - min) / 2.0;
    }

    private double median(int[] arr, int n) {
        if (n == 0)
            return Double.NaN; // error

        if (n % 2 == 0)
            return (arr[n / 2] + arr[n / 2 - 1]) / 2.0;
        return arr[n/2];
    }

}