leetcode 4 寻找两个有序数组的中位数

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=1l=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]的元素都在AB合并之后的前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。由于在AB中分别有k/2-1个元素小于m,所以m即是第k小的数。(这里可能有人会有疑问,如果k为奇数,则m不是中位数。这里是进行了理想化考虑,在实际代码中略有不同,是先求k/2,然后利用k-k/2获得另一个数。)

通过上面的分析,我们即可以采用递归的方式实现寻找第k小的数。此外我们还需要考虑几个边界条件:

  • 如果A或者B为空,则直接返回B[k-1]或者A[k-1]
  • 如果k1,我们只需要返回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)二分法求两个排序数组的中位数(难);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值