求两个有序数组的中位数

There are two sorted arrays nums1 and nums2 of size m and n respectively.

Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)).

Example 1:
nums1 = [1, 3]
nums2 = [2]

The median is 2.0
Example 2:
nums1 = [1, 2]
nums2 = [3, 4]

The median is (2 + 3)/2 = 2.5

双数组

我们设:
Ci为第i个数组的割。
Li为第i个数组割后的左元素.
Ri为第i个数组割后的右元素。

这里写图片描述

如何从双数组里取出第k个元素

首先Li<=Ri是肯定的(因为数组有序,左边肯定小于右边)
如果我们让L1<=R2 && L2<=R1
这里写图片描述
那么左半边 全小于右半边,如果左边的元素个数相加刚好等于k,那么第k个元素就是Max(L1,L2),参考上面常识1。
如果 L1>R2,说明数组1的左边元素太大(多),我们把C1减小,把C2增大。L2>R1同理,把C1增大,C2减小。

双数组的奇偶

中值的关键在于,如何处理奇偶性,单数组的情况,我们已经讨论过了,那双数组的奇偶问题怎么办,m+n为奇偶处理方案都不同,

让数组恒为奇数

有没有办法让两个数组长度相加一定为奇数或偶数呢?

其实有的,虚拟加入‘#’(这个trick在manacher算法中也有应用),让数组长度恒为奇数(2n+1恒为奇数)。
Ps.注意是虚拟加,其实根本没这一步,因为通过下面的转换,我们可以保证虚拟加后每个元素跟原来的元素一一对应
这里写图片描述

将其映射回原数组位置

每个数字可以通过除以2映射回原来数组位置
不仅如此,割更容易,如果割在‘#’上等于割在2个元素之间,割在数字上等于把数字划到2个部分。

奇妙的是不管哪种情况:

Li = (Ci-1)/2
Ri = Ci/2

例:
1. 割在4/7之间‘#’,C = 4,L=(4-1)/2=1 ,R=4/2=2
刚好是4和7的原来位置!
2. 割在3上,C = 3,L=(3-1)/2=1,R=3/2 =1,刚好都是3的位置!

剩下的事情就好办了,把2个数组看做一个虚拟的数组A,目前有2m+2n+2个元素,割在m+n+1处,所以我们只需找到m+n+1位置的元素和m+n+2位置的元素就行了。
左边:A[m+n+1] = Max(L1+L2)
右边:A[m+n+2] = Min(R1+R2)

Mid = (A[m+n+1]+A[m+n+2])/2
= (Max(L1+L2) + Min(R1+R2) )/2

分治的思路

只要c1或c2确定,另一个也就确定了。
必须选择较短的数组做二分, 原因是,在较短的阵列上,所有的位置都是中位数可能的切割位置,但在较长的阵列上,左右位置太靠右的位置根本不可能合理切割。 例如,[1],[2 3 4 5 6 7 8]。 显然,2和3之间的切割是不可能的,因为如果你这样做的话,较短的阵列没有那么多元素来平衡[3 4 5 6 7 8]部分。 因此,对于较长阵列作为第一次切割的基础,必须进行范围检查。 在较短的阵列上执行操作就简单多了,不需要任何检查。 另外,只在较短的阵列上移动会产生运行时复杂度为O(log(min(N1,N2)))。

  • L1>R2,把C1减小,C2增大。—> C1向左二分

  • L2>R1,把C1增大,C2减小。—> C1向右二分

double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int n = nums1.size();
        int m = nums2.size();
        if(n > m)   //保证数组1一定最短
            return findMedianSortedArrays(nums2,nums1);
        int L1,L2,R1,R2,c1,c2,lo = 0, hi = 2*n;  //我们目前是虚拟加了'#'所以数组1是2*n长度
        while(lo <= hi)   //二分
        {
            c1 = (lo+hi)/2;  //c1是二分的结果
            c2 = m+n- c1;
            L1 = (c1 == 0)?INT_MIN:nums1[(c1-1)/2];   //map to original element
            R1 = (c1 == 2*n)?INT_MAX:nums1[c1/2];
            L2 = (c2 == 0)?INT_MIN:nums2[(c2-1)/2];
            R2 = (c2 == 2*m)?INT_MAX:nums2[c2/2];

            if(L1 > R2)
                hi = c1-1;
            else if(L2 > R1)
                lo = c1+1;
            else
                break;
        }
        return (max(L1,L2)+ min(R1,R2))/2.0;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值