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)
我们只使用了常数空间来存储中间变量。