[LeetCode]寻找两个正序数组的中位数

题目描述

  给定两个大小分别为 m 和 n 的 正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的中位数 。算法的时间复杂度应该为 O(log (m+n)) 。

两种传统方法

  1. 使用归并的方式,合并两个有序数组,得到一个大的有序数组。大的有序数组的中间位置的元素,即为中位数。

  2. 不需要合并两个有序数组,只要找到中位数的位置即可。由于两个数组的长度已知,因此中位数对应的两个数组的下标之和也是已知的。维护两个指针,初始时分别指向两个数组的下标 00 的位置,每次将指向较小值的指针后移一位(如果一个指针已经到达数组末尾,则只需要移动另一个数组的指针),直到到达中位数的位置。

  上述的两种方法也能解决问题,并且容易想到,但是其时间复杂度不满足要求,这里不再解释和介绍。

二分思想分析问题

  分析上面的情形,对于两个数组,我们常规的思路是对两个数组分别设置两个指针,然后不断地移动指向较小数字的指针,指到找到中间位置。这个方法是逐个元素逐个元素的统计,直到找到中间位置。
  那么,我们能否找到一种方法,可以一批一批的统计元素,直到找到中间位置呢?答案是可以的。
  中位数的特点很清楚,给定一个数组,只要确定中间位置的索引,那么就可以确定中位数。但是现在是两个数组的中位数,不过其思想是相同的,即都是寻找指定位置。(偶数个元素需要寻找两个位置然后求平均值,奇数个元素需要寻找一个位置即可,就是中间位置)。现在我们的问题变成了:给定两个正序数组,寻找第 k 小的元素。
  对于这个问题,我们可以分别在两个数组左边获取一个 k 2 − 1 \frac{k}{2}-1 2k1 长度的子数组,这时会有下面的重要性质,请仔细体会一下:
  有两个数组:

[1,2,6,7,8,11,15]
[3,5,6,8,9,10,12,17,19]

  假设现在的 k = 10,得到的两个子数组(含有元素 k 2 − 1 \frac{k}{2}-1 2k1 个)如下:

[1,2,6,7]
[3,5,6,8]

  可以看出题目的意思是找到第 10 小的元素。
  可以看出两个子数组的最后一个元素 7 < 8,那么对于 7 这个子数组:

[1,2,6,7]

  一定是位于前 10 小的元素之中,为什么这么说?请想象一下,第一个数组中比 7 大的元素一定在 7 的右边,第二个数组中比 8 大的元素一定在 8 的右边,当然上述比 7 大的和比 8 大的都没有表示出来。那么谁一定比 7 小?当然是 7 左边的元素以及 7,因为每个数组的长度是 k 2 − 1 \frac{k}{2}-1 2k1,所以可以肯定含 7 的这个子数组可以移除了,现在的目标变成了从新的两个数组中寻找第 (10 - 4) 小的元素!
  上面的过程可以递归进行,不过需要注意终止条件:当目标变成寻找第 1 小的元素时,只需要返回两个数组中第一个元素的最小值即可。
  除此之外,还是需要一些问题要考虑,加入有两个数组:

[1]
[2,3,4,5,6,7,8]

   目的是寻找第 6 小的元素,显然需要取的子数组的大小是 k 2 − 1 = 2 \frac{k}{2}-1=2 2k1=2,此时明显超出了第一个数组的维度,这种情况下,将第一个数组取完即可,不必一定要取 k 2 − 1 \frac{k}{2}-1 2k1 大小,当然此时能够移去的元素数量也要具体情况具体分析了!
   到现在,事情还差一点就能完美了,还是上面的例子,在经历一步之后,变为下面的问题,从:

[]
[2,3,4,5,6,7,8]

   中找到第 5 小的元素,次数第一个元素已经为空,就不要在取 k 2 − 1 \frac{k}{2}-1 2k1 子数组了(提醒一下,现在的 k 是 5,不是 6 了!)问题本质上就是从一个正序数组中找到第 5 小的元素,直接返回就好了!

代码

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int len1 = nums1.size(),len2 = nums2.size();
        double res1 = findK(nums1,0,len1-1,nums2,0,len2-1,(len1+len2)/2+1);
        if((len1+len2)%2==1) return res1;
        else{
            double res2 = findK(nums1,0,len1-1,nums2,0,len2-1,(len1+len2)/2);
            return (res1+res2)/2.0;
        }
    }

    double findK(vector<int>& nums1,int start1,int end1,vector<int>& nums2,int start2,int end2,int K){
        // 处理越界问题
        if(start1>end1) return nums2[start2+K-1];
        if(start2>end2) return nums1[start1+K-1];

        // 终止条件
        if(K == 1) return nums1[start1]<nums2[start2]?nums1[start1]:nums2[start2];

        // 处理形如 [1;2,3,4,5,6,7] 的 K/2-1 越界问题
        if(start1+K/2-1>end1){
            int first = nums1[end1];
            int second = nums2[start2+K/2-1];   
            if(first <= second)
                return findK(nums1,end1+1,end1,nums2,start2,end2,K-end1-1);
            else
                return findK(nums1,start1,end1,nums2,start2+K/2,end2,K-K/2);
        }
        else if(start2+K/2-1>end2){
            int first = nums1[start1+K/2-1];
            int second = nums2[end2];   
            if(first <= second)
                return findK(nums1,start1+K/2,end1,nums2,start2,end2,K-K/2);
            else
                return findK(nums1,start1,end1,nums2,end2+1,end2,K-end2-1);
        }
        else{
            int first = nums1[start1+K/2-1];
            int second = nums2[start2+K/2-1];   
            if(first <= second)
                return findK(nums1,start1+K/2,end1,nums2,start2,end2,K-K/2);
            else
                return findK(nums1,start1,end1,nums2,start2+K/2,end2,K-K/2);
        }
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值