LeetCode刷题第4题【寻找两个正序数组的中位数】---解题思路及源码注释

LeetCode刷题第4题【寻找两个正序数组的中位数

】—解题思路及源码注释

结果预览

代码执行效果

一、题目描述

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

提示:

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

二、解题思路

1、问题理解

我们需要在两个已排序的数组中找到中位数。因为数组是正序的,合并后的数组也是有序的。中位数通常是数组中间位置的元素,如果数组的长度是偶数,则是中间两个数的平均值。

关键要求:

时间复杂度要求为 O(log(m+n))
这提示我们不能直接将两个数组合并后排序,这样会导致 O(m+n) 的时间复杂度。
我们需要使用二分查找的思想来优化解法。

2、解题思路

为了满足 O(log(m+n)) 的时间复杂度要求,我们可以通过 二分查找 来优化解决方案。具体思路如下:

1、确保较短数组进行二分查找:

  • 通过将 nums1 和 nums2 中较短的数组作为 nums1,我们可以减少二分查找的范围,从而降低计算量。

2、中位数的划分:

  • 假设我们对两个数组进行合并后划分,划分的位置保证左边的元素个数等于右边的元素个数(或者左边比右边多一个元素,适用于奇数长度的情况)。
  • 使用二分查找确定 nums1 和 nums2 的划分点,使得左边的最大元素小于等于右边的最小元素。

3、判断合适的划分:

  • 对于每个划分点,判断:
    • nums1[i-1] <= nums2[j] 和 nums2[j-1] <= nums1[i]
    • 如果满足这些条件,说明我们找到了正确的划分。

4、计算中位数:

  • 如果合并后的数组总长度为奇数,中位数是左边部分的最大值。
  • 如果合并后的数组总长度为偶数,中位数是左边最大值和右边最小值的平均。

三、代码实现及注释

1、源码实现

class Solution {
public:
    double findMedianSortedArrays(std::vector<int>& nums1, std::vector<int>& nums2) {
        // 确保 nums1 是较短的数组
        if (nums1.size() > nums2.size()) {
            std::swap(nums1, nums2);
        }

        int m = nums1.size(), n = nums2.size();
        int left = 0, right = m;

        while (left <= right) {
            int i = (left + right) / 2; // nums1 中的划分点
            int j = (m + n + 1) / 2 - i; // nums2 中的划分点

            // 获取 nums1 和 nums2 划分点的左右元素
            int nums1Left = (i == 0) ? INT_MIN : nums1[i - 1];
            int nums1Right = (i == m) ? INT_MAX : nums1[i];
            int nums2Left = (j == 0) ? INT_MIN : nums2[j - 1];
            int nums2Right = (j == n) ? INT_MAX : nums2[j];

            if (nums1Left <= nums2Right && nums2Left <= nums1Right) {
                // 找到正确的划分
                if ((m + n) % 2 == 0) {
                    // 偶数长度,返回中位数为两部分最大最小值的平均
                    return (std::max(nums1Left, nums2Left) + std::min(nums1Right, nums2Right)) / 2.0;
                } else {
                    // 奇数长度,返回左边部分的最大值
                    return std::max(nums1Left, nums2Left);
                }
            } else if (nums1Left > nums2Right) {
                // nums1 的划分点太大,移动 `right`
                right = i - 1;
            } else {
                // nums1 的划分点太小,移动 `left`
                left = i + 1;
            }
        }

        // 如果没有找到答案,返回 0
        return 0.0;
    }
};

2、代码解释

1、数组交换:

  • 我们首先确保 nums1 是较短的数组,如果 nums1 比 nums2 长,我们就交换它们。这样可以使得二分查找的范围更小,优化计算。

2、二分查找:

  • 我们对 nums1 进行二分查找,计算出一个划分点 i,然后计算
    j,使得左边部分的大小和右边部分的大小相等(或左边部分比右边多一个元素,适用于奇数长度的情况)。
  • 在每次迭代中,检查是否满足以下两个条件:
    • nums1[i-1] <= nums2[j]
    • nums2[j-1] <= nums1[i]
  • 如果满足条件,表示已经找到了合适的划分点,计算并返回中位数。

3、中位数计算:

  • 如果合并后的总长度是偶数,返回左边部分的最大值和右边部分的最小值的平均值。
  • 如果合并后的总长度是奇数,返回左边部分的最大值。

四、执行效果

代码执行效果

1、时间和空间复杂度分析

时间复杂度: O(log(min(m, n)))
其中 m 和 n 分别是两个数组的长度。我们只对较短的数组 nums1 进行二分查找,因此时间复杂度是 O(log(min(m, n)))
空间复杂度: O(1)
我们只使用了常数空间来存储中间变量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值