Leetcode-4. 寻找两个正序数组的中位数

482 阅读3分钟

题目

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

算法的时间复杂度应该为 O(log (m+n)) 。

示例 1:

输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2

示例 2:

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

示例 3:

输入:nums1 = [0,0], nums2 = [0,0]
输出:0.00000

示例 4:

输入:nums1 = [], nums2 = [1]
输出:1.00000

示例 5:

输入:nums1 = [2], nums2 = []
输出:2.00000

来源:力扣(LeetCode)
链接:leetcode-cn.com/problems/me…
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路

  1. 基本思路 用一条边界把两个数组分为左右两个相等的部分,若为奇数个,则让左边比右边多一个,然后通过边界值去求中位数。
    image.png

偶数情况
中位数 = (左边最大值 + 右边最小值)/ 2

奇数情况
中位数 = 左边最大值

  1. 特殊情况处理
  • i或者j左边没有值,则左边最大值为j-1或者i-1的位置上的值 image.png

此时左边最大值为:
maxLeft = B[j-1]

image.png

此时左边最大值为:
maxLeft = A[i-1]

  • i或者j右边没有值,其处理方式与上述类似
  1. ij的关系分析
  • 对于偶数情况 image.png

i+j = (m+n)/2

  • 对于奇数情况 image.png

i+j = (m+n+1)/2

  • 合并 因为计算机处理int类型除法,偶数情况给分子加1并不会影响其结果,因此奇数偶数情况都可以用下面的关系表示:

i+j = (m+n+1)/2

因此:j = (m+n+1)/2 - 1

  1. 问题转化 因为只要知道了i就能确定j的位置,因此问题转换为只要找到准确的i即可。因为找到i就可以计算j,有了i和j就可以求得左边最大值和右边最小值,然后计算中位数。

对于在一个数组中,找到一个i的位置,可以使用二分法来进行查找,因此该算法的时间复杂度是O(log(min(m, n)))的。

  1. 整体流程

image.png

算法

public double findMedianSortedArrays(int[] A, int[] B) {
    // 第一步:保证第一个数组的长度要小于第二个数组,可以利用递归来实现
    int m = A.length;
    int n = B.length;
    if (m > n) {
        return findMedianSortedArrays(B, A);
    }

    // 第二步:初始化i的区间
    int iMin = 0, iMax = m;
    while (iMin <= iMax) {
        // 对于i使用二分法,取区间中间值
        int i = (iMin + iMax) / 2;
        int j = (m + n + 1) / 2 - i;
        // 交叉判断当前的i位置是否是正确的位置
        if (j != 0 && i != m && B[j-1] > A[i]) {
            // i需要增大
            iMin = i + 1;
        } else if (i != 0 && j != n && A[i-1] > B[j]) {
            iMax = i - 1;
        } else {
            // 找到了合适的位置,先找左边最大值
            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 -1.0;
}