April01xxx

导航

Median of Two Sorted Arrays

1.题目


最近开始做一些算法训练,感觉自己太鶸.来看看这个题目:给定两个已排序的数组,求其中位数

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

先不管时间复杂度,看到题目的第一反应是将两个数组合并排序,判断两数组长度之和是奇数还是偶数就能得到结果,不过很明显这样做时间复杂度是O(m+n),而题目要求O(log(m+n)),自己想了半天没结果去看了答案,再次感觉自己太鶸.解答的思路很精妙,将其翻译一下记录在此做个备案.

 

2.思路


先附上原解决方法的链接: Median of Two Sorted Arrays

设有两个数组A,B升序排列,其元素个数分别为m,n,为简单起见,假设0<=m<=n.要求中间的数M,很显然,在这个数的左边的数都小于等于M,右边的数都大于等于M,而且M左右两边的元素个数相等(若m+n为偶数,则M代表中间的两个数).如果我们能把A,B两个数组等分为左右两部分且左边的所有元素都小于等于右边的元素,那么就得到了M.这样我们将问题转换为:

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]

求合适i,j,满足以下两个条件:

  1. i+j = m-i+n-j (0<=i<=m)
  2. B[j-1] <= A[i] and A[i-1] <= B[j]

其中i表示left(A)的元素个数,j表示left(B)的元素个数.

若i=0,则表示数组A左半边为空;j=0表示数组B左半边为空.任意一边为空的情况下,则条件2可以弱化:例如i=0,则A的左边为空,则无须判断A[i-1]是否小于等于B[j].

 

显然若m+n为偶数,则肯定存在合适的i,j使得条件1成立;若m+n为奇数则不存在这样的i,j(奇数长度的数组没法二等分),是不是奇数情况无法求解呢?实际上也是可以求解的,若长度为奇数,则最后拆分的结果要么左边多一个元素,要么右边多一个元素。

  1. 若左边多一个元素,则M=max(left(A), left(B)), left(A)表示A的左边所有元素,left(B)表示B左边所有元素,此时有 i + j = (m+n+1)/2。
  2. 若右边多一个元素,则M=min(right(A), right(B)), right(A)表示A的右边所有元素,right(B)表示B的右边所有元素,此时有 i + j = (m+n)/2。

注意到即使m+n为偶数,上述两个等式仍然成立(整数除法截断,例如4/2=2, 5/2=2).网络上针对该问题给出的代码大部分都是采用情形1的拆分方法,那我就写个第二种方法的吧,另外为了加快查找满足条件的i,j的值,采用二分查找,贴上代码:

#define max(x, y)   ((x) > (y) ? (x) : (y))
#define min(x, y)   ((x) > (y) ? (y) : (x))

double findMedianSortedArrays(int* A, int m, int* B, int n) {
    int i, j;
    int begin, end, half;
    int max_of_left, min_of_right;

    /* 若两数组长度不满足我们的假设m<=n,则交换 */
    if (m > n)
        return findMedianSortedArrays(B, n, A, m);

    if (n == 0)
        return 0;

    begin = 0;
    end = m;
    half = (m + n) / 2;
    while (begin <= end) {
        i = (begin + end) / 2;      /* 二分查找 */
        j = half - i;   /* 采用拆分后右边可能多一个元素的做法, i+j = (m+n)/2 */

        if (i > 0 && A[i - 1] > B[j]) {
            /* i太大,缩小二分查找的最大范围 */
            end = i - 1;
        } else if (i < m && B[j - 1] > A[i]) {
            /* 因为 j = (m+n)/2 - i, 若i < m, 则 j > 0.
               i太小,增加二分查找的最小范围. */
            begin = i + 1;
        } else {
            /* 条件1,2均满足,找到了合适的i, j. */
            if (i == m)
                min_of_right = B[j];
            else if (j == n)
                min_of_right = A[i];
            else
                min_of_right = min(A[i], B[j]);

            if ((m + n) & 0x1)
                return min_of_right;

            if (i == 0)
                max_of_left = B[j - 1];
            else if (j == 0)
                max_of_left = A[i - 1];
            else
                max_of_left = max(A[i - 1], B[j - 1]);

            return (max_of_left + min_of_right) / 2.0;
        }
    }
    return 0;
}

 

posted on 2018-07-06 14:49  April01xxx  阅读(106)  评论(0编辑  收藏  举报