【LeetCode】Day123-寻找两个正序数组的中位数

题目

4. 寻找两个正序数组的中位数【困难】

题解

直接找中位数位置

思路
最直接的想法是合并两个数组,找到中间位置的数;
更进一步,我们其实不需要合并数组,只需要找到中间位置的数即可,则可以借鉴归并排序的思路,利用两个指针,根据情况不断向后移动,直到到达中位数的位置。
思路很好想,但是对于奇数偶数的讨论比较复杂,即这个中位数到底怎么表示?

算法
看着挺简单的代码,实际上内容还挺丰富的,要是我自己写估计得啰里啰嗦写一大堆,精选题解写的非常的精炼
对于len是奇数的情况,中位数出现在下标为len/2的位置;
len是偶数的情况,中位数在下标为len/2-1和len/2之间,
因此,总结下来,不论奇偶,都要经过len/2+1个数才能找到中位数,选择用循环k找到下标为len/2的数。除此之外,为了记录偶数情况中len/2下标的前一个数,设置pre变量记录该数。

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m=nums1.length,n=nums2.length,len=m+n,i=0,j=0;
        int pre=-1,cur=-1;
        for(int k=0;k<=len/2;k++){
            pre=cur;
            if(i<m&&(j>=n||nums1[i]<nums2[j]))
                cur=nums1[i++];
            else
                cur=nums2[j++];
        }
        //偶数
        if(len%2==0)
            return (double)(pre+cur)/2;
        //奇数
        return cur; 
    }
}

时间复杂度: O ( m + n ) O(m+n) O(m+n)

空间复杂度: O ( 1 ) O(1) O(1)

二分查找

单论题来说还算不上困难,难的是如何实现时间复杂度为O(log(m+n))?这个log就让人想到二分,问题可以转化成寻找两个有序数组中的第k小的数,其中k为len/2或者len/2+1
假设两个有序数组分别是 A 和 B。要找到第 k 个元素,我们可以比较 A[k/2−1] 和 B[k/2−1],根据比较结果分为以下这三种情况:

  • A[k/2−1]<B[k/2−1],则A[k/2−1]前至多k-2个元素比它小,肯定不是第k小,因此排除A[0]到A[k/2−1]
  • A[k/2−1]>B[k/2−1],排除B[0]到B[k/2−1]
  • A[k/2−1]==B[k/2−1],可以归入第一种情况处理
    在这里插入图片描述

可以看到,比较 A[k/2−1] 和 B[k/2−1] 之后,可以排除 k/2 个不可能是第 k 小的数,查找范围缩小了一半。同时,我们将在排除后的新数组上继续进行二分查找,并且根据我们排除数的个数,减少 k 的值,这是因为我们排除的数都不大于第 k 小的数。

这里有三个特殊情况需要单独讨论:

  • A[k/2−1] 或 B[k/2−1]越界,那么可以选取对应数组中的最后一个元素。根据排除数的个数减少k的值,而不能直接将k减去k/2。
  • 一个数组为空,直接返回另一个数组中的第k小的元素。
  • k=1,返回两个数组首元素的最小值。

举个例子,过程如下:
首先讨论len=m+n的奇偶性,令mid=len/2,若len是奇数,k就是mid+1;若是偶数,k为第mid数和第(mid+1)数的1/2。
在这里插入图片描述

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m=nums1.length,n=nums2.length,len=m+n,mid=len/2;
        if(len%2==1)
            return (double)getKth(nums1,nums2,mid+1);
        else
            return (getKth(nums1,nums2,mid)+getKth(nums1,nums2,mid+1))/2.0;
    }
    //获得两个数组中第k小的元素
    public int getKth(int[] nums1,int[] nums2,int k){
        int i=0,j=0,m=nums1.length,n=nums2.length;
        while(true){
        	//特殊情况
            if(i==m)
                return nums2[j+k-1];//nums1元素都被排除,返回nums2的中位数
            if(j==n)
                return nums1[i+k-1];
            if(k==1)
                return Math.min(nums1[i],nums2[j]);
            //一般情况
            int newI=Math.min(i+k/2,m)-1,newJ=Math.min(j+k/2,n)-1;//避免越界,所以取了i+k/2和m中的最小值
            if(nums1[newI]<=nums2[newJ]){
                k-=(newI-i+1);//如果i+k/2没有越界,其实就是减去了k/2;如果越界,则是减去了排除数的个数
                i=newI+1;//排除了newI前的所有元素
            }
            else{
                k-=(newJ-j+1);
                j=newJ+1;
            }
        }
    }
}

时间复杂度: O ( l o g ( m + n ) ) O(log(m+n)) O(log(m+n))

空间复杂度: O ( 1 ) O(1) O(1)

p.s 做了二分查找的解法才觉得,困难题不愧是困难题,真是块难啃的硬骨头

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值