leetcode打卡11:题号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

题目解法一:

(自己的解法,不符合复杂度要求)
1.由于自身对递归和分治思想比较不熟悉,所以一开始肯定写不出符合复杂度的算法,因为这个复杂度一看就是要用递归去做
2.该算法的思想其实就是获取到两个数组的长度之和,然后记录中位数的位数(即是第几位数)
3.之后开始定义i,j分别同时遍历两个数组,逐一进行元素比较,哪个小就意味着在前面,进行遍历,对应的数组下标后移即可。在遍历的过程中进行计数,当计数到中位数的位置时就输出即可。
4.该算法的算法复杂度应该是O(m+n) 空间复杂度也是O(1)

public static double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int sumLength = nums1.length + nums2.length;
        int numStore = 0;//中位数存储
        int sum = 0;//计数现在定位到了多少个数了
        int targetIndex = ((sumLength+1)/2) - 1;//中位数所在的索引
        for(int i = 0, j = 0; i < nums1.length || j < nums2.length;){

            if(i == nums1.length){
                numStore = nums2[j];
                j += 1;
            }else if(j == nums2.length){
                numStore = nums1[i];
                i += 1;
            }else{
                if(nums1[i] <= nums2[j]) {
                    numStore = nums1[i];
                    i += 1;
                }else{
                    numStore = nums2[j];
                    j += 1;
                }

            }
            sum += 1;

            if(sum == targetIndex){//找到中位数了

                if(sumLength %2 == 0){//总共偶数个数
                    int n = Math.min(nums1[i],nums2[j]);
                    return (numStore + n)/2.0;

                }else{//总共奇数个数
                    return numStore;
                }

            }
        }
        throw new IllegalArgumentException("No two sum solution");
    }

题目解法二(官方解法):

1.首先我们要理解中位数的概念,其实就是将一组有序数组变成两节,左边的永远比右边的小(左边最大的小于右边最小的),这就是中位数的概念。
2.设计如下

  • 我们假设从下标 i 开始将数组A切分成俩个数组LeftA,RightA,那么在这两个数组中,LeftA一定小于RightA,在这个条件下,如果LeftA和RightA的长度又正好相同(长度为偶数的情况下),那么我们就可以知道中位数为A【i-1】+A【i】的平均值。如果长度为奇数,那么就让LeftA的长度比RightA多1,那么中位数就为A【i-1】
    在这里插入图片描述

  • 同理,我们从 j 下标开始将数组B切分为两个数组LeftB,RightB,根据上面的思想我们也可以得到一些结论
    在这里插入图片描述

  • 那么如何将这两个数组融合在一起呢?其实很简单,只要我们同时在A,B中进行切分,将LeftA,LeftB都放在左边作为一个LeftPart,将RightA,RightB都放在右边作为一个RightPart,这时候我们来看会出现什么结果
    在这里插入图片描述

  • 设A数组的长度为m,B数组的长度为n,那么我们就可以得出 i 的范围是【0,m】

  • 在这个新的LeftPart,RightPart中,为了准确找到中位数的长度,我们要满足两个条件:

    1. Len(LeftPart) = Len(RightPart) 或者
      Len(LeftPart) = Len(RightPart)+ 1
    2. Max(LeftPart)<= Min(RightPart)
  • 然后我们将这两个条件用数字公式翻译过来:

    1. i + j = m - i + n - j 或 i + j = m - i + n - j +1
      再转化一次就是:j = (m+n+1)/ 2 - i ( i 和 j 的关系就出来了)
    2. B【j-1】<=A【i】 && B【j】>= A【i-1】
  • 直到这里我们本题的思路就很清楚了,我们采用递归的方法,不断的找到这个 i 的值,每次都进行判断此时是否符合这两个条件,如果符合那就说明此时切分的很合理,那就意味着可以直接找到中位数了!

  • 但是有几个需要注意的地方:

    1. 由于j = (m+n+1)/ 2 - i ,而 i 的范围是【0,m】,我们要确保 j 不能是负值,所以就必须保证n > m,也就是说必须把短的数组放在前面,长的数组放在后面,其实就是进行一个长度判断,转一下即可
    2. 第二个问题其实就是关于一些临界点的问题,比如i = m,j = n的问题,这些问题我们可以留到后面再讨论
  • 关于如何找到 i 这就是该算法的一个核心思想,这里采用二叉树的搜索思想实现:

    1. 首先定义iMin = 0,iMin = m,这两个就规定了i的范围,我们每次都取i = (iMin+iMax)/ 2
    2. 然后先满足第一个长度条件求j:j = (m+n+1)/ 2 - i
    3. 然后去判断此时的i是否符合我们的那两个条件,如果发现不符合,那就根据不同的情况去调整iMin ,iMax的范围,这样我们最后就可以找到符合条件的i值了
  • 对于每次判断i是否符合条件一共会出现三种情况:
    在这里插入图片描述

    1. B[j−1]>A[i]:此时意味着A【i】太小了,应该在左半部分,所以那就意味着我们切A的时候,应该多切一点,i应该增大,那么我们应该将范围调整为【i+1,iMax】
    2. A[i−1]>B[j]:此时意味着A【i-1】太大了,应该在右半部分,所以我们切A的时候应该少切一点,i应该减小,将范围调整为【iMin,i-1】
    3. B[j−1]≤A[i] 且A[i−1]≤B[j]:那就意味着我们找到了符合要求的切分点,那么此时就可以不用再寻找了,接下来就是找中位数了
  • 算法实现框架:(这里不是最终代码,只是为了更好的理解)

  public static double findMedianSortedArrays1(int[] nums1, int[] nums2) {
        int m = nums1.length;
        int n = nums2.length;
        if(n < m){ //一定要把长的放前面
            int temp[] = nums1; nums1 = nums2; nums2 = temp;
            int tempa = m; m = n; n = tempa;
        }

        int iMin = 0, iMax = m, halflen = (m + n + 1) / 2;
        while(iMin <= iMax){
            //通过已有的关系式,设置i ,j的大小  i每次都从中间砍一刀
            int i = (iMin + iMax) / 2;
            int j = halflen - i;
            //疑问 为什么i < iMax   i > iMin    【1,3】 【2】 报错
            if( i < iMax && nums2[j - 1] > nums1[i]){ //B[j-1] > A[i]  LeftA部分砍少了 i右移
                iMin =i + 1;
            }else if(i > iMin && nums2[j] < nums1[i - 1]){//B[j] < A[i - 1] leftA部分砍多了 i左移
                iMax =i - 1;
            }else{ //i砍得正好
                //接下来这部分就是求中位数了
                
            }
        }
        return  0.0;
    }
  • 对于求中位数部分而言其实很简单,首先无论是总长度是奇数还是偶数,我们都需要先求出LeftPart中的max,因为如果是奇数,那么它就是中位数,如果是偶数,我们还需要求出RightPart的min,这样就可以求出中位数了
  • 然后我们这里就出现了刚刚提到的如果i = m, j = n怎么办,直接上代码,基本上都可以看得懂!
public static double findMedianSortedArrays1(int[] nums1, int[] nums2) {
    int m = nums1.length;
    int n = nums2.length;
    if(n < m){ //一定要把长的放前面
        int temp[] = nums1; nums1 = nums2; nums2 = temp;
        int tempa = m; m = n; n = tempa;
    }

    int iMin = 0, iMax = m, halflen = (m + n + 1) / 2;
    while(iMin <= iMax){
        //通过已有的关系式,设置i ,j的大小  i每次都从中间砍一刀
        int i = (iMin + iMax) / 2;
        int j = halflen - i;
        //疑问 为什么i < iMax   i > iMin    【1,3】 【2】 报错
        //因为i如果此时等于iMax,呢么iMin加完之后也等于iMax,
        if( i < iMax && nums2[j - 1] > nums1[i]){ //B[j-1] > A[i]  LeftA部分砍少了 i右移
            iMin =i + 1;
        }else if(i > iMin && nums2[j] < nums1[i - 1]){//B[j] < A[i - 1] leftA部分砍多了 i左移
            iMax =i - 1;
        }else{ //i砍得正好
            int maxLeft = 0;
            //这一部分的判断其实就是怕数组越界了
            if(i == 0){ //若左半部分没有A A全部在右边
                maxLeft = nums2[j - 1];
            }else if(j == 0){ //若左边部分没有B B全在右边
                maxLeft = nums1[i - 1];
            }else {
                maxLeft = Math.max(nums1[i - 1],nums2[j - 1]);
            }

            if( (m + n)%2 != 0){//如果是总长度为奇数  那么maxLeft就是中位数  因为分的时候左部分会比有部分多一个
                return  maxLeft;
            }

            //如果总长度为偶数
            int minRight = 0;
            if(i == m){ //右部分没有A
                minRight = nums2[j];
            }else if(j == n){//右部分没有B
                minRight = nums1[i];
            }else {
                minRight = Math.min(nums1[i],nums2[j]);
            }
            return  (maxLeft + minRight) / 2.0;

        }
    }
    return  0.0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值