LeetCode 4. 寻找两个正序数组的中位数【c++/java详细题解】

668 阅读2分钟

图片.png 「这是我参与2022首次更文挑战的第15天,活动详情查看:2022首次更文挑战

1、题目

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

示例 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

提示:

  • nums1.length == m
  • nums2.length == n
  • 0 <= m <= 1000
  • 0 <= n <= 1000
  • 1 <= m + n <= 2000
  • -106 <= nums1[i], nums2[i] <= 106

进阶: 你能设计一个时间复杂度为 O(log (m+n)) 的算法解决此问题吗?

2、思路

(递归) O(log(n+m))O(log(n+m))

找出两个正序数组的中位数等价于找出两个正序数组中的第k小数。如果两个数组的大小分别为nm ,那么第 k = (n + m)/2 小数就是我们要求的中位数。

如何寻找第k小的元素?

过程如下:

1、考虑一般情况,我们在 nums1nums2数组中各取前k/2个元素

<img src="力扣500题刷题笔记.assets/image-20210722154205811.png" alt="image-20210722154205811" style="zoom:50%;" />

我们默认nums1数组比nums2数组的有效长度小 。nums1数组的有效长度从i开始,nums2数组的有效长度从j开始,其中[i,si - 1]nums1数组的前k / 2个元素,[j, sj - 1]nums2数组的前 k / 2个元素。

2、接下来我们去比较nums1[si - 1]nums2[sj - 1]的大小。

  • 如果nums1[si - 1] > nums2[sj - 1] ,则说明 nums1 中取的元素过多,nums2 中取的元素过少。因此nums2 中的前 k/2个元素一定都小于等于第 k 小数,即nums2[j,sj-1]中元素。我们可以舍去这部分元素,在剩下的区间内去找第k - k / 2小的元素,也就是说第k小一定在[i,n][sj,m]中。
  • 如果nums1[si - 1] <= nums2[sj - 1],同理可说明nums2中的前 k/2个元素一定都小于等于第 k 小数,即nums1[i,si-1]中元素。我们可以舍去这部分元素,在剩下的区间内去找第k - k / 2小的元素,也就是说第k小一定在[si,n][j,m]中。

3、递归过程2,每次可将问题的规模减少一半,最后剩下的一个数就是我们要找的第k小数。

递归边界:

  • nums1数组为空时,我们直接返回nums2数组的第k小数。
  • k == 1时,且两个数组均不为空,我们返回两个数组首元素的最小值,即min(nums1[i], nums2[j])

奇偶分析:

  • 当两个数组元素个数的总和total为偶数时,找到第total / 2left和第total / 2 + 1right,结果是(left + right / 2.0)

  • total为奇数时,找到第total / 2 + 1小,即为结果。

时间复杂度分析: k=(m+n)/2k=(m+n)/2,且每次递归 kk 的规模都减少一半,因此时间复杂度是O(log(m+n))O(log(m+n)).

3、c++代码

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int tot = nums1.size() + nums2.size();
        if (tot % 2 == 0) {
            int left = find(nums1, 0, nums2, 0, tot / 2);
            int right = find(nums1, 0, nums2, 0, tot / 2 + 1);
            return (left + right) / 2.0;
        } else {
            return find(nums1, 0, nums2, 0, tot / 2 + 1);
        }
    }

    int find(vector<int>& nums1, int i, vector<int>& nums2, int j, int k) {
        if (nums1.size() - i > nums2.size() - j) return find(nums2, j, nums1, i, k);
        if (k == 1) {
            if (nums1.size() == i) return nums2[j];
            else return min(nums1[i], nums2[j]);
        }
        if (nums1.size() == i) return nums2[j + k - 1];
        int si = min((int)nums1.size(), i + k / 2), sj = j + k - k / 2;
        if (nums1[si - 1] > nums2[sj - 1])
            return find(nums1, i, nums2, sj, k - (sj - j));
        else
            return find(nums1, si, nums2, j, k - (si - i));
    }
};

4、java代码

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int total = nums1.length + nums2.length;
        if(total % 2 == 0)
        {
            int left = f(nums1,0,nums2,0,total / 2);
            int right = f(nums1,0,nums2,0,total / 2 + 1);
            return (left + right) / 2.0;
        }
        else return f(nums1,0,nums2,0,total / 2 + 1);
    }
    static int f(int[] nums1,int i,int[] nums2,int j,int k)
    {
        //默认第一个是小的
        if(nums1.length - i > nums2.length - j) return f(nums2,j,nums1,i,k);
        //当第一个数组已经用完
        if(nums1.length == i) return nums2[j + k - 1];
        //当取第1个元素
        if(k == 1) return Math.min(nums1[i],nums2[j]);

        int si = Math.min(nums1.length,i + k / 2),sj = j + k - k / 2;
        if(nums1[si - 1] > nums2[sj - 1])
        {
            return f(nums1,i,nums2,sj,k - (sj - j));
        }
        else
        {
            return f(nums1,si,nums2,j,k - (si - i));
        }
    }
}

原题链接: 4. 寻找两个正序数组的中位数 在这里插入图片描述