1.题目
给定两个大小为 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
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/median-of-two-sorted-arrays
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
2.我的题解
可以考虑使用最大堆最小堆,
- 左边一个最大堆,右边一个最小堆;
- 数据小于左堆顶插入左堆,数据大于等于左堆顶插入右堆。
- 调整两个堆的堆顶(到另一个堆里)以保证两个堆的大小相差不超过1,并且如果大小不相等,可以优先保证左堆的大小比右堆大1;
- 可利用两个堆顶数值求出中位数。
该方法有以下问题:
- 适合求数据流(一个一个到来)的中位数;
- 没有用上有序条件;
- 设数据规模为n,(新来一个数后求中位数)一次操作复杂度为
O(logn)
,n次操作显然大于对数数量级,因为每个元素都要访问,是线性数量级;
3.别人的题解
3.1 二分法
将问题转化为求两个排序数组的第k
小数值,数组a
长度为m
,数组b
长度为n
:
- 确定边界:假设有
i,j
满足:i+j=k;a[i-1]<=b[j];b[j-1]<=a[i]
,那么a[i-1],b[j-1]
的较大值就是第k
小;(暂不考虑特殊情况,比如i=0
)这里i,j
取不到,它其实意味着该位置之前有i,j
个元素,因此0<=i<=n,0<=j<=m
;可求得初始位置l=max(0,k-n),r=min(k,n)
,l
是数组a
的索引,r
是数组b
的索引; - 二分循环:令
mid=(l+r)/2
,比较a[mid]和b[k-mid-1]
,这里需要注意到数组的a[mid]
和b[k-mid]
,因为这两个数的索引相加为k
,故需要判断前一个位置b[k-mid-1]
是否小于a[mid]
,如果是,那么r
左移,如果不是,说明a[mid]
太小了,那么l
右移。 - 得到答案:
l<r
时一直循环。最终得到的l
就是答案,只需要求解a[l-1]
和b[k-l-1]
的较大值。当然需要判断特殊情况l=1
和l=k
防止越界。
class Solution {
int findKMin(vector<int>&nums1, vector<int>&nums2, int k) {
int n = nums1.size(), m = nums2.size();
if (k<0 || k>n + m)return -1;
int l = max(0, k - m), r = min(k, n);
while (l<r) {
int mid = (l + r) >> 1;
if (nums2[k - mid - 1]>nums1[mid])l = mid+1;
else r = mid;
}
int max1 = l == 0 ? INT_MIN : nums1[l - 1];
int max2 = l == k ? INT_MIN : nums2[k - l - 1];
return max(max1, max2);
}
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int len=nums1.size()+nums2.size();
if(len&1)//odd
return findKMin(nums1,nums2,(len>>1)+1);
else //even
return 0.5*(findKMin(nums1,nums2,(len>>1))+findKMin(nums1,nums2,(len>>1)+1));
}
};
3.2 二分法递归实现
版权声明:本文为CSDN博主「yutianzuijin」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yutianzuijin/article/details/11499917
首先假设数组A和B的元素个数都大于k/2
,我们比较A[k/2-1]和B[k/2-1]
两个元素,这两个元素分别表示A
的第k/2
小的元素和B
的第k/2
小的元素。这两个元素比较共有三种情况:>、<
和=
。如果A[k/2-1]<B[k/2-1]
,这表示A[0]
到A[k/2-1]
的元素都在A
和B
合并之后的前k
小的元素中。换句话说,A[k/2-1]
之前不可能大于两数组合并之后的第k小值,所以我们可以将其抛弃。
当A[k/2-1]>B[k/2-1]
时存在类似的结论,抛弃B
的前面一部分。
当A[k/2-1]=B[k/2-1]
时,我们已经找到了第k
小的数,也即这个相等的元素,我们将其记为m
。由于在A
和B
中分别有k/2-1
个元素小于m
,所以m
即是第k
小的数。(这里可能有人会有疑问,如果k
为奇数,则m
不是中位数。这里是进行了理想化考虑,在实际代码中略有不同,是先求k/2
,然后利用k-k/2
获得另一个数。)
通过上面的分析,我们即可以采用递归的方式实现寻找第k
小的数。此外我们还需要考虑几个边界条件:
- 如果
A
或者B
为空,则直接返回B[k-1]
或者A[k-1]
; - 如果
k
为1
,我们只需要返回A[0]
和B[0]
中的较小值; - 如果
A[k/2-1]=B[k/2-1]
,返回其中一个;
class Solution {
int findKMin(vector<int>&nums1, int l1, int r1,
vector<int>&nums2, int l2, int r2, int k) {
//always suppose:len1<len2
int len1 = r1 - l1, len2 = r2 - l2;
if (len1>len2)return findKMin(nums2, l2, r2, nums1, l1, r1, k);
if (len1 == 0)return nums2[l2 + k - 1];
if (k == 1)return min(nums1[l1], nums2[l2]);
int mid1 = min(k / 2, len1), mid2 = k - mid1;
if (nums1[l1 + mid1 - 1]<nums2[l2 + mid2 - 1]) {
return findKMin(nums1, l1+mid1, r1, nums2, l2, r2, k - mid1);
}
else if (nums1[l1 + mid1 - 1]>nums2[l2 + mid2 - 1]) {
return findKMin(nums1, l1, r1, nums2, l2+mid2, r2, k - mid2);
}
else
return nums1[l1 + mid1 - 1];
}
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int len=nums1.size()+nums2.size();
if(len&1)//odd
return findKMin(nums1,0,nums1.size(),nums2,0,nums2.size(),(len>>1)+1);
else //even
return 0.5*(findKMin(nums1,0,nums1.size(),nums2,0,nums2.size(),(len>>1))+
findKMin(nums1,0,nums1.size(),nums2,0,nums2.size(),(len>>1)+1));
}
};
4.总结与反思
(1)二分法求两个排序数组的中位数(难);