给定两个大小为 m
和 n
的正序(从小到大)数组 nums1
和 nums2
。
请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))
。
你可以假设 nums1
和 nums2
不会同时为空。
示例 1:
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5
下面这个复杂度是O(m+n)
,勉强过了:
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
vector<double> temp;
int m = nums1.size();
int n = nums2.size();
int i=0, j=0;
// 既然两个数组都排过序了,直接遍历中间即可
while((i+j) <= (m+n)/2){
if(i == m){
temp.push_back(nums2[j]);
j++;
continue;
}
if(j == n){
temp.push_back(nums1[i]);
i++;
continue;
}
if(nums1[i] < nums2[j]){
temp.push_back(nums1[i]);
i++;
}
else{
temp.push_back(nums2[j]);
j++;
}
}
int l = temp.size();
if((m+n)%2==0)
return (temp[l-2] + temp[l-1]) / 2;
else
return temp[l-1];
}
};
参考讨论区,结合二分搜索,实现题目要求复杂度:
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int m = nums1.size();
int n = nums2.size();
// 当m+n为奇数时,target1=target2,当为偶数时,正好包含两个中位数
int target1 = (m+n+1)/2;
int target2 = (m+n+2)/2;
return (helper(nums1, nums2, 0, 0, target1) + helper(nums1, nums2, 0, 0, target2)) / 2.0;
}
//查找两个有序数组中的第target个排序数字
int helper(vector<int>& nums1, vector<int>& nums2, int start1, int start2, int target){
// 其中一个数组为空
if(start1>=nums1.size())
return nums2[start2+target-1];
if(start2>=nums2.size())
return nums1[start1+target-1];
if(target==1)
return min(nums1[start1], nums2[start2]);
// 二分,各在两个数组中查找一半,可能数组长度小于目标排序的一半
int mid1 = (start1 + target/2 - 1) < nums1.size() ? nums1[start1+target/2-1] : INT_MAX;
int mid2 = (start2 + target/2 - 1) < nums2.size() ? nums2[start2+target/2-1] : INT_MAX;
if(mid1 < mid2)
return helper(nums1, nums2, start1+target/2, start2, target-target/2);
else
return helper(nums1, nums2, start1, start2+target/2, target-target/2);
}
};
还有一种切割的思路,假设nums1
的长度小于nums2
,将两数组合并后的有序数组,中位数位置是(m+n+1)/2
,设在合并数组的中位数前,总共有原nums1
数组k
个数字,则相应有nums2
数组(m+n+1)/2-k
个数字,转化为在nums1
中寻找这样的切割点k
,使得nums1
切割点左侧的数字小于nums2
对应切割点右侧的数字,同时nums2
切割点左侧的数字同样小于nums1
切割点左侧的数字,(由于原来的两个数组都是排序的,因此同一个数组切割点左侧数字一定小于右侧数字),这样就使得两数组切割点左侧总共有(m+n+1)/2
个数字了,并且均小于切割点右侧数字,对应于中位数。
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
{
// 1. 保持num1是长度较短的数组
if (nums1.size() > nums2.size()) return findMedianSortedArrays(nums2, nums1);
const int m = nums1.size();
const int n = nums2.size();
int left=0, right=m, lmax1, lmax2, rmin1, rmin2;
while(left <= right)
{
// 2. 从 num1 中取一个 i 位置
int i = (left+right)/2;
// 3. 根据中位数性质,此时 num2 应取在 j 处,才可能让nums1+nums2左右两侧相等
int j = (m + n + 1)/2 - i;
// 4. 按照当前的划分,将分割线周围的4个元素都取出,准备进行比较,调整区间
lmax1 = i == 0? INT_MIN : nums1[i-1];
lmax2 = j == 0? INT_MIN : nums2[j-1];
rmin1 = i >= m? INT_MAX : nums1[i];
rmin2 = j >= n? INT_MAX : nums2[j];
// 5.1 nums1的左边最大 大于 nums2的右边最小,因此 i 取大了,往前移动
if (lmax1 > rmin2) right = i-1;
// 5.2 nums2的左边最大 大于 nums1的右边最小,因此 i 取小了,往后移动
else if (lmax2 > rmin1 ) left = i+1;
// 5.3 四个数已经平衡,可以结束
else break;
}
if ((m + n) % 2 == 0)
{
return (max(lmax1, lmax2)+min(rmin1, rmin2))/2.0;
}
else
{
return max(lmax1, lmax2);
}
}
};