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
思路:
1.看到本题一个最简单的想法显然就是将两个数组合并为一个数组然后再找中位数就很简单了,但本题限制了时间复杂度那么显然遍历两个数组进行合并的想法是走不通的,时间复杂度至少为O(m + n)了。
2.看到要求的时间复杂度为log,再加上题目给的数组是有序的,能够很敏锐的想起二分查找。本题我们显然必须得同时对两个数组进行二分查找(因为不能进行合并),那么如何判定找到了两个数组共同的中位数就是解决本题的关键。
3.首先我们一般找中位数本质上首先得看数组长度,本题中因为是两个数组所以实际长度应当是两个数组之和m + n,如果为奇数,那么我们要找的实际上就是第(m + n) / 2 + 1个数;如果为偶数,那么我们要找的实际上就是第(m + n) / 2个数与第(m + n) / 2 + 1个数之和除以2。我们假设k = (m + n) / 2,那么实际上我们本题要找的就是两个数组中第k小或者第k和第k+1小的数。
4.联想到二分查找中我们每一次寻找中间位置数字后都能完全排除掉一边的结果,在本题中我们显然也要根据每一次的结果来舍弃掉一部分元素。为了防止舍弃掉正确答案,我们每一次寻找两个数组中下标为第k / 2 - 1位置的元素并进行比较。假设nums1[k / 2 - 1] < nums2[k / 2 - 1],那么此时最多也就只有nums1的前k / 2 - 1个元素和nums2的前k / 2 - 1个元素比nums1[k / 2 -1]小,即最多有k - 2个元素比nums1[k / 2 - 1]小,此时一次性能最多排除k - 1个元素,保证了不会排除掉正确答案。
5.舍弃掉一部分元素后,显然我们的起始位置需要发生变化,因此4中寻找下标为第k / 2 - 1位置的元素是从“当前”起始位置开始算起的。每一次我们舍弃掉一部分后新的“当前”起始位置应当是之前的起始位置加上k / 2;或者已经到达边界,当前数组中的所有元素都会被排除,直接就是当前数组长度为“当前”起始位置。
class Solution {
public:
int getKElement(vector<int>& nums1, vector<int>& nums2, int k){
int m = nums1.size();
int n = nums2.size();
//两个指向数组元素的指针,永远指向数组“当前”的起始位置元素
int index1 = 0;
int index2 = 0;
while(true){
//如果其中一个数组指针已经指向末尾,说明该数组元素已经全部被舍去
//直接返回另一个数组“当前”起始位置开始的第k个数
if(index1 == m){
return nums2[index2 + k - 1];
}
if(index2 == n){
return nums1[index1 + k - 1];
}
//如果k = 1,那么实际上要返回的就是两个数组中“当前”起始位置里较小的那一个
if(k == 1){
return min(nums1[index1], nums2[index2]);
}
//正常情况,找两个数组中从“当前”起始位置开始算起第k / 2 - 1位置的元素,
//此时能够保证最多有k - 2个元素比这两个中较大的元素小
int newIndex1 = min(index1 + k / 2 - 1, m - 1);
int newIndex2 = min(index2 + k / 2 - 1, n - 1);
int pivot1 = nums1[newIndex1];
int pivot2 = nums2[newIndex2];
//如果此时num1 <= num2,那么数组1从“当前”起始位置开始算起的前k / 2 - 1个元素都可以被舍弃
//此时k需要减少k / 2,并且更新数组“当前”的起始位置
if(pivot1 <= pivot2){
k -= newIndex1 - index1 + 1;
index1 = newIndex1 + 1;
}
else{
k -= newIndex2 - index2 + 1;
index2 = newIndex2 + 1;
}
}
return 0;
}
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int k = nums1.size() + nums2.size();
if(k % 2 != 0){
return getKElement(nums1, nums2, (k + 1) / 2);
}
else{
return (getKElement(nums1, nums2, k/ 2 + 1) + getKElement(nums1, nums2, k / 2 )) / 2.0;
}
}
};