`

Leetcode-计算两个排序数组的中位数

阅读更多

题目描述

给定两个大小为 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

算法实现

我的实现思路

  • 根据元素总个数,计算出中位数的位置。
    • 如果总个数为奇数,则中位数取中间一个即可。
    • 如果总个数为偶数,则需要取中间两个数之和的平均值。
  • 假设桌上有两副已排好序的扑克牌,从上到下由小到大排序。那么
    • 从两副牌的顶部各取一张,进行比较后取最小的一张牌。
    • 从取出最小牌的那副再取一张,与另一副前一次抽取的牌进行比较,然后取最小的一张牌。
    • 如此循环,我们拿到的牌将是从小到大的顺序,直至拿到牌的数量与我们要找的中位数位置相同即可停止。
    • 此时,如果判断元素总数为偶数个,则再取一张牌后停止。
  • 最后,如果是奇数个元素,则直接返回。如果是偶数个元素,则返回两个数之和的平均值。
/**
 * 个人实现版本.
 * 
 * @param nums1
 * @param nums2
 * @return
 */

public static double findMedianSortedArraysV1(int[] nums1, int[] nums2) {
    int[] resultArray = new int[2];
    int idx1 = 0;
    int idx2 = 0;
    int currentCount = 0;

    int size1 = (nums1 == null) ? 0 : nums1.length;
    int size2 = (nums2 == null) ? 0 : nums2.length;

    boolean evenResult = true// 总数是否为偶数个
    int medianNum = (size1 + size2) / 2// 中位数的位置
    if ((size1 + size2) % 2 != 0) {
        medianNum++;
        evenResult = false// 总数为奇数个
    }
    if (medianNum == 0) {
        throw new IllegalArgumentException("median num not found");
    }

    int currentValue = 0;
    while (true) {
        if (idx1 <= size1 - 1 && idx2 <= size2 - 1) {
            if (nums1[idx1] <= nums2[idx2]) {
                currentValue = nums1[idx1++];
            } else {
                currentValue = nums2[idx2++];
            }
            currentCount++;
        } else if (idx1 <= size1 - 1) {
            currentValue = nums1[idx1++];
            currentCount++;
        } else if (idx2 <= size2 - 1) {
            currentValue = nums2[idx2++];
            currentCount++;
        }

        // 相等则找到中位数
        if (currentCount == medianNum) {
            resultArray[0] = currentValue;
            if (!evenResult) { // 如果总数为偶数,需要找到中间的2个数取平均值
                break;
            }
        } else if (currentCount > medianNum && evenResult) { // 找到第二数
            resultArray[1] = currentValue;
            break;
        }
    }

    double resultValue = evenResult ? Double.valueOf(resultArray[0] + resultArray[1]) / 2 : Double.valueOf(resultArray[0]);

    return resultValue;
}

个人实现的版本,时间复杂度为O((m+n)/2),比题目的要求慢一些,不过思路相对清晰一些。

习题答案

个人理解如下:

假设有数组A,长度为m,任意位置i将数组分为左右两部分。数组B,长度为n,任意位置j将数组分为左右两部分。

那么i>=0,且i<=m。j>=0,且j<=n。i和j可以理解为数组从小到大的第几个数,例如i=1代表A[0],即数组A的第一个数。

如果要找到A和B各自的中位数,需要满足i=m-i,j=n-j,即左右两部分的个数是相同的,此时中间的数即为中位数。当然,实现的时候仍需要考虑奇数、偶数个数组元素的问题。

由于我们是在A和B两个数组合并之后找中位数,因此找到中位数需要满足i+j=m-i+n-j(或:m-i+n-j+1,奇数个元素的情况)。

另外,题目要求的时间复杂度是log(m+n),所以很容易想到二分查找(如折半查找、二叉查找树)。所以问题的关键点就是,在不合并数组并做好排序(时间复杂度为O(m)、O(n)或O(m+n),肯定不满足需求)的情况下,如何利用二分查找来实现该算法。

由前面我们推导出的公式可知,j=(m+n)/2-i或j=(m+n+1)/2-i。

事实上,如果要简单的理解可以不用这么麻烦的推导。

假设数组元素的总个数为奇数,那么中位数必然在第(m + n + 1) / 2个。

此时,要找到中位数,显然i+j=(m + n + 1) / 2,这个结果与上述的推导是一致的。有了这个公式,二分查找就好办了。分而治之,先计算出数组A的中间位置i=(0+m)/2。那么,此时j就可以通过前面的公式计算出来了。

二分查找时,如果满足B[j−1]<=A[i]且A[i−1]<=B[j],则表示找到中位数了,结束查找。如果不满足,则在i的前或后半部分继续进行上述的二分查找,直至满足条件即可。

为什么说满足B[j−1]<=A[i]且A[i−1]<=B[j],则表示找到中位数了?

因为A[i−1]+B[j−1]代表数组的一半元素,共有i+j个。那么只要满足最后一个条件,我们就找到中位数了。最后一个条件,就是A[i−1]和B[j−1]都要小于等于另一半元素的值,这里我们只要保证同时小于另一半的最小值A[i]和B[j](或者说是下一个值)即可。

/**
 * 习题答案
 * 
 * @param A
 * @param B
 * @return
 */

public double findMedianSortedArrays(int[] A, int[] B) {
    int m = A.length;
    int n = B.length;
    if (m > n) { // to ensure m<=n
        int[] temp = A; A = B; B = temp;
        int tmp = m; m = n; n = tmp;
    }
    int iMin = 0, iMax = m, halfLen = (m + n + 1) / 2;
    while (iMin <= iMax) {
        int i = (iMin + iMax) / 2;
        int j = halfLen - i;
        if (i < iMax && B[j-1] > A[i]){
            iMin = iMin + 1// i is too small
        }
        else if (i > iMin && A[i-1] > B[j]) {
            iMax = iMax - 1// i is too big
        }
        else { // i is perfect
            int maxLeft = 0;
            if (i == 0) { maxLeft = B[j-1]; }
            else if (j == 0) { maxLeft = A[i-1]; }
            else { maxLeft = Math.max(A[i-1], B[j-1]); }
            if ( (m + n) % 2 == 1 ) { return maxLeft; }

            int minRight = 0;
            if (i == m) { minRight = B[j]; }
            else if (j == n) { minRight = A[i]; }
            else { minRight = Math.min(B[j], A[i]); }

            return (maxLeft + minRight) / 2.0;
        }
    }
    return 0.0;
}

算法实现

https://github.com/qiuzj/leetcode/blob/master/src/main/java/cn/javaee/leetcode/q4/median_of_two_sorted_arrays/MedianOfTwoSortedArrays.java

https://leetcode-cn.com/problems/median-of-two-sorted-arrays/description/

 

 

转载请注明来源:http://zhanjia.iteye.com/blog/2427289

 

个人公众号

二进制之路

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics