问题描述:
寻找两个正序数组的中位数
给定两个大小分别为 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
-10^6^ <= nums1[i], nums2[i] <= 10^6^
解题思路及代码:
- 第一种思路就是先合并数组,然后再通过下标找到中位数,这种方法的时间复杂度为O(m+n)
- 第二种思路就是不合并数组,只去两数组中找到中位数所在的位置,找到后再取出即可,此时可以使用二分法来找到目标元素同时降低时间复杂度,时间复杂度为O(log(m+n))
第一种方法时间复杂度不满足题目要求,不过也可解题其代码如下:
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
vector<int> nums3;
for(const auto &e:nums1)
{
nums3.push_back(e);
}
for(const auto &e:nums2)
{
nums3.push_back(e);
}
sort(nums3.begin(),nums3.end());
if(nums3.size() % 2 != 0)
{
return (double)nums3[nums3.size() / 2];
}
else
{
double mid = ((double)nums3[nums3.size() / 2 - 1] + (double)nums3[nums3.size() / 2]) / 2;
return mid;
}
return 0;
}
};
第二种方法最终目标就是要找到中位数的位置,这其中涉及到m+n的奇偶性
问题,情况如下:
- 当m+n为奇数时只需找到
k=m+n/2+1
的数即可,其中k代表的是两数组所有数中由小到大取到的第k个数 - 当m+n为偶数时需要找到
k=m+n/2
和k= m+n/2+1
,然后再对两值的和折半即为结果
再来讨论一下二分法,二分法的核心就是通过缩小划分区间,找到目标元素,对本题来说用例如下
上述情况为一般的二分法查找过程,对于本题来说还需要考虑到两种极端情况,就是当两数组元素个数差距较大时,且元素少的数组中元素较小时,在查找过程中就会出现走到较小数组末尾的情况,此时上述方法就会导致越界访问,故需要单独处理,用例如下
思路讲完了,以下是代码实现
class Solution {
public:
double Binarysearch(vector<int>& nums1, vector<int>& nums2, int midIndex) {
int index1 = 0;
int index2 = 0;
int m = nums1.size();
int n = nums2.size();
while (true)
{
//当一边数组已经全部排除时
if (index2 == nums2.size())
{
return nums1[index1 + midIndex - 1];
}
if (index1 == nums1.size())
{
return nums2[index2 + midIndex - 1];
}
//当无法继续二分时
if (midIndex == 1)
{
return min(nums1[index1], nums2[index2]);
}
//取每次需要比较的两下标
int newIndex1 = midIndex / 2 - 1 + index1;
int newIndex2 = midIndex / 2 - 1 + index2;
bool flag = false;
//判断是否超过数组大小
if (newIndex1 > m - 1)
{
newIndex1 = m - 1;
flag = true;
}
else if (newIndex2 > n - 1)
{
newIndex2 = n - 1;
flag = true;
}
//更新策略
if (nums1[newIndex1] >= nums2[newIndex2])
{
if (flag)
{
midIndex -= newIndex2 - index2 + 1;
}
else
{
midIndex -= midIndex / 2;
}
index2 = newIndex2 + 1;
}
else
{
if (flag)
{
midIndex -= newIndex1 - index1 + 1;
}
else
{
midIndex -= midIndex / 2;
}
index1 = newIndex1 + 1;
}
}
}
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int length = nums1.size() + nums2.size();
if (length % 2 != 0)
{
return Binarysearch(nums1, nums2, length / 2 + 1);
}
else
{
return (Binarysearch(nums1, nums2, length / 2) + Binarysearch(nums1, nums2, length / 2 + 1)) / 2;
}
return 0;
}
};
实际上k值的更新本质上就是减去上次排除的元素个数,不需要区分情况处理,故最终简化代码如下:
class Solution {
public:
double Binarysearch(vector<int>& nums1, vector<int>& nums2, int midIndex) {
int index1 = 0;
int index2 = 0;
int m = nums1.size();
int n = nums2.size();
while (true)
{
//当一边数组已经全部排除时
if (index2 == nums2.size())
{
return nums1[index1 + midIndex - 1];
}
if (index1 == nums1.size())
{
return nums2[index2 + midIndex - 1];
}
//当无法继续二分时
if (midIndex == 1)
{
return min(nums1[index1], nums2[index2]);
}
//取每次需要比较的两值,不能超过数组大小
int newIndex1 = min(midIndex / 2 - 1 + index1, m - 1);
int newIndex2 = min(midIndex / 2 - 1 + index2, n - 1);
if (nums1[newIndex1] >= nums2[newIndex2])
{
midIndex -= newIndex2 - index2 + 1;
index2 = newIndex2 + 1;
}
else
{
midIndex -= newIndex1 - index1 + 1;
index1 = newIndex1 + 1;
}
}
}
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int length = nums1.size() + nums2.size();
if (length % 2 != 0)
{
return Binarysearch(nums1, nums2, length / 2 + 1);
}
else
{
return (Binarysearch(nums1, nums2, length / 2) + Binarysearch(nums1, nums2, length / 2 + 1)) / 2;
}
return 0;
}
};