题目
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。
提示:
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(nlogn),不符合题目要求
二分法
根据时间复杂度O(logn),可以知道只有二分法可以做到这样,同时因为两个数组均是正序排列,符合二分的条件,因此我们可以坚定的选择二分操作。
因此可以确定基本思路是对两个数组二分判断左右两个哪个位于中位数。
但是因为两个数组相互割裂,无法进行直观比较,找到最中间的数。
因此需要回归原始方式,采用查数的方式找到中位数。
即在有序数列中的中位数:如果所有数的总数len为奇数,则中位数前面有len/2个比他小的数,它后面有len/2个比它大的数;如果是len为偶数,则这里有两个中位数,左边的中位数左边有len/2-1个比他小的数,右边的中位数左边有len/2个比它小的数。
我们要做的则是计算某一个数x,它左边到底有多少个比它小的数,如果符合中位数左边数量,说明这就是中位数。
- 对每个中位数,前面有len/2个数比它小 ,即它从小到大排到第k=len/2+1位,我们先将这k个数的指标平均分给两个数组进行查找,即从两个数组k/2-1(如果k/2-1越界,则从数组最右侧查找)下标开始查找。
举例如下:
nums1:[1,2,5,7,9], nums2:[0,4,6,8],
len=9, k=5, k/2-1=1, 建立标记索引index1=0, index2=0(后面解释)
此时nums1[index1+k/2-1]=2,nums2[index2+k/2-1]=4
- 指向指定下标后,排除较小的那个值loser和它在这个数组中之前的所有值。因为loser是最多比它数组前面的k/2-1个数和另一个数组前面k/2-1个数大,即最多大于2(k/2-1)个数。
由2<4,因此此时的nums1[index1+k/2-1]只比1和0大,即只大于2(k/2-1)个数,不是中位数。
将2和1排除(因不清楚nums2中4前面是比2大还是小所以不能排除)
若nums2的0换个数组[3,4,6,8]就可能是3,所以说2最多大于2(k/2-1)个数
- 排除几个数后,此时要排除的k个数就减小了
nums1:[1,2,5,7,9], nums2:[0,4,6,8]
len=9, k=k-2=3(排除1,2), k/2-1=0
index1=2, index2=0(索引使用于记录排除后的开始下标)
- 重复以上操作
- 终止情况:
- k==1。因为k只剩1个时,此时这个数就是中位数,输出两个数组指向中最小的值。
- index1==nums1.size()溢出。输出nums1[index1+k-1],即只排除nums1中k个数
- index2==nums1.size()溢出。输出nums2[index2+k-1]
同时需要注意特殊情况:
- nums1数组为空时,直接输出nums2的中位数
- nums2数组为空时,直接输出nums1的中位数
感觉像二分又不像二分,主要思想是每次将要排查的数各分一半给两个数组
代码
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int len = nums1.size()+nums2.size();
if(len%2!=0)
return findOne(nums1, nums2, len/2+1);
else
return (findOne(nums1, nums2, len/2)+findOne(nums1, nums2, len/2+1))/2.0;
}
double findOne(vector<int>& nums1, vector<int>& nums2, int k){
int len1 = nums1.size(), len2 = nums2.size();
int index1 = 0, index2 = 0;
while(1){
if(index1==len1)
return nums2[index2+k-1];
if(index2==len2)
return nums1[index1+k-1];
if(k==1)
return min(nums1[index1], nums2[index2]);
int new1 = min(len1-1, index1+k/2-1);
int new2 = min(len2-1, index2+k/2-1);
if(nums1[new1]>nums2[new2]){
k -= new2 - index2 + 1;
index2 = new2 + 1;
}
else if(nums1[new1]<=nums2[new2]){
k -= new1 - index1 + 1;
index1 = new1 + 1;
}
}
}
};