在不等长的两个正序数组中找第k大的数

在不等长的两个正序数组中找第k大的数

题目:

给定两个大小分别为 n 和 m的正序(从小到大)数组nums1nums2`。请你找出并返回这两个正序数组的 第k个大的数 。并且做到时间复杂度尽量小。

一般方法是二分查找,时间复杂度大概是O(log(n)*log(m)),本方法可以做到O(log(min{n,m}))

本方法用到了两个等长正序数组找上中位数这个基本算法,下面先介绍这个算法。

两个等长的正序数组找上中位数:

代码:

大前提:等长、正序!!!

private static int findFirstMedium(int[] nums1, int l1, int r1, int[] nums2, int l2, int r2, int length) {
    if (length == 1)
        return Math.min(nums1[l1], nums2[l2]);
    else {
        int mid1 = (r1 - l1) / 2 + l1;
        int mid2 = (r2 - l2) / 2 + l2;
        if (length % 2 == 0) {
            if (nums1[mid1] == nums2[mid2])
                return nums1[mid1];
            else if (nums1[mid1] > nums2[mid2]) {
                return findFirstMedium(nums1, l1, mid1, nums2, mid2 + 1, r2, mid1 - l1 + 1);
            } else {
                return findFirstMedium(nums1, mid1 + 1, r1, nums2, l2, mid2, mid2 - l2 + 1);
            }
        } else {
            if (nums1[mid1] == nums2[mid2]) {
                return nums1[mid1];
            } else if (nums1[mid1] > nums2[mid2]) {
                if (nums2[mid2] > nums1[mid1 - 1]) {
                    return nums2[mid2];
                } else {
                    return findFirstMedium(nums1, l1, mid1 - 1, nums2, mid2 + 1, r2, r2 - (mid2 + 1) + 1);
                }
            } else {
                if (nums1[mid1] > nums2[mid2 - 1]) {
                    return nums1[mid1];
                } else {
                    return findFirstMedium(nums1, mid1 + 1, r1, nums2, l2, mid2 - 1, r1 - (mid1 + 1) + 1);
                }
            }

        }
    }
}
总体思想:

递归,递归的出口是:当每个数组的长度都为1,则返回数组元素中较小的那个(很好理解吧,只有两个数,两个数都是中位数,上中位数就是较小的那个元素)

用例子讲思想:

设正序数组nums1和nums2,它们的长度都是length;

求它们的上中位数(两个正序数组合并在一起,它们的总长度肯定是偶数,那么就会有两个中位数,上中位数指的是第一个中位数)。

image-20221110150419480

这里的数字均为在本数组中第几大的数,不是具体数值)

如果length是奇数:

找到各自的上中位数,即3和3‘,下面讨论:

1、3==3’,则3或者说3’,就是要找的上中位数,返回即可;

2、3>3‘,下面讨论这种情况下,哪些是可能成为上中位数的,{1,2},{3’,4‘,5’}这五个数都有可能,这个时候两个数组的长度不相等,于是我们想办法将它们变成等长:

先判断3‘是否大于等于2,如果大于等于,则直接返回3’;否则,剔除3’,剩下{1,2},{4‘,5’}递归去求,但是此时length是偶数,就得用下面的方法去求。

3、3<3’,类似第2步。

如果length是偶数:

image-20221110152635178

同样先找到各自的上中位数,即3和3’,下面讨论:

1、3==3’,则3或者说3’,就是要找的上中位数,返回即可;

2、3>3’,下面讨论这种情况下,哪些是可能成为上中位数的,{1,2,3},{4‘,5’,6’}这六个数都有可能,并且它们的length是奇数,用length是奇数的方法继续递归;

3、3<3’,类似第2步。

本题解法:

代码:

前提:nums1的长度大于nums2的长度!!!

private static double function(int[] nums1, int[] nums2, int k) {
    int n=nums1.length;
    int m=nums2.length;
    if(k>=1&&k<=n){
        return findFirstMedium(nums1,0,k-1,nums2,0,k-1,k);
    }else if(k>n&&k<=m){
        int l2=k-n-1;
        if(nums2[l2]>=nums1[n-1]){
            return nums2[l2];
        }else {
            return findFirstMedium(nums1,0,n-1,nums2,l2+1,k-1,n);
        }
    }else {
        int l1=k-m-1;
        int l2=k-n-1;
        if(nums1[l1]>=nums2[m-1])
            return nums1[l1];
        if(nums2[l2]>=nums1[n-1])
            return nums2[l2];
        return findFirstMedium(nums1,l1+1,n-1,nums2,l2+1,m-1,(m-1)-(l2+1)+1);
    }

}
思想:

分类讨论,n是短数组nums1的长度,m是长数组nums2的长度。

还是通过例子介绍:

image-20221110153838199

(都表示是本数组中第几大的值,不表示具体元素数值)

1、1<=k<=n

先找到可能成为上中位数的所有情况,如果k为5,则可能成为上中位数的有{1,2,3,4,5},{1’,2’,3’,4’,5’},符合等长且正序,可以调用findFirstMedium方法;

一般情况:

nums1:(0,k-1);nums2:(0,k-1);这里指下标;

2、n<k<=m

如果k为8,则可能成为上中位数的有:{1,2,3,4,5,6},{2’,3’,4’,5’,6’,7’,8’},此时并不能直接调用findFirstMedium方法,要先转化为等长正序;所以我们得在nums2中剔除一个,将2’单独考虑,如果2’>6,则说明2’就是要找的上中位数,直接返回;否则将2’剔除,再调用findFirstMedium方法;

3、K>m

如果k为13,则可能成为上中位数的有:{2,3,4,5,6},{7’,8’,9’,10’,11’},此时虽然满足等长且正序,**但是不能直接调用findFirstMedium方法!!!**为什么呢?我们来看,nums1排除了1个元素,nums2排除了六个元素,如果将排出后的数组调用findFirstMedium方法去求,求出的数排出后的数组中第5大的数,1+6+5等于12,并不是我们要的k(13)!!!!!!

所以,我们先验证排出后的数组各自第一个元素是不是要找的上中位数,如果2是,则返回2,;如果7’是,则返回7’;否则将{3,4,5,6},{8’,9’,10’,11’}调用findFirstMedium方法求上中位数

分析:

时间复杂度:O(log(min{n,m}))

实战检验:

4. 寻找两个正序数组的中位数 - 力扣(LeetCode)

我的解答(时间复杂度java提交击败100%):

class Solution {
     public  double findMedianSortedArrays(int[] nums1, int[] nums2) {


      int sumLen = nums1.length + nums2.length;

        if(sumLen==1){
            return nums1.length==0?nums2[0]:nums1[0];
        }
        if(nums1.length==0){
            if(sumLen%2==1){
                return nums2[(sumLen+1)/2-1];
            }else {
                return (nums2[sumLen/2-1]+nums2[sumLen/2+1-1])*1.0/2;
            }
        }
        if(nums2.length==0){
            if(sumLen%2==1){
                return nums1[(sumLen+1)/2-1];
            }
            else {
                return (nums1[sumLen/2-1]+nums1[sumLen/2+1-1])*1.0/2;
            }
        }

        int k = 0;
        if (sumLen % 2 == 1) {
            k = (sumLen + 1) / 2;
            if (nums1.length <= nums2.length)
                return function(nums1, nums2, k);
            else {
                return function(nums2, nums1, k);
            }

        } else {
            k = sumLen / 2;
            if (nums1.length <= nums2.length)
                return (function(nums1, nums2, k) + function(nums1, nums2, k + 1)) / 2;
            else {
                return (function(nums2, nums1, k) + function(nums2, nums1, k + 1)) / 2;
            }
        }
    }

    private  double function(int[] nums1, int[] nums2, int k) {
        int n=nums1.length;
        int m=nums2.length;
        if(k>=1&&k<=n){
            return findFirstMedium(nums1,0,k-1,nums2,0,k-1,k);
        }else if(k>n&&k<=m){
            int l2=k-n-1;
            if(nums2[l2]>=nums1[n-1]){
                return nums2[l2];
            }else {
                return findFirstMedium(nums1,0,n-1,nums2,l2+1,k-1,n);
            }
        }else {
            int l1=k-m-1;
            int l2=k-n-1;
            if(nums1[l1]>=nums2[m-1])
                return nums1[l1];
            if(nums2[l2]>=nums1[n-1])
                return nums2[l2];
            return findFirstMedium(nums1,l1+1,n-1,nums2,l2+1,m-1,(m-1)-(l2+1)+1);
        }

    }


    

    private  int findFirstMedium(int[] nums1, int l1, int r1, int[] nums2, int l2, int r2, int length) {
        if (length == 1)
            return Math.min(nums1[l1], nums2[l2]);
        else {
            int mid1 = (r1 - l1) / 2 + l1;
            int mid2 = (r2 - l2) / 2 + l2;
            if (length % 2 == 0) {
                if (nums1[mid1] == nums2[mid2])
                    return nums1[mid1];
                else if (nums1[mid1] > nums2[mid2]) {
                    return findFirstMedium(nums1, l1, mid1, nums2, mid2 + 1, r2, mid1 - l1 + 1);
                } else {
                    return findFirstMedium(nums1, mid1 + 1, r1, nums2, l2, mid2, mid2 - l2 + 1);
                }
            } else {
                if (nums1[mid1] == nums2[mid2]) {
                    return nums1[mid1];
                } else if (nums1[mid1] > nums2[mid2]) {
                    if (nums2[mid2] > nums1[mid1 - 1]) {
                        return nums2[mid2];
                    } else {
                        return findFirstMedium(nums1, l1, mid1 - 1, nums2, mid2 + 1, r2, r2 - (mid2 + 1) + 1);
                    }
                } else {
                    if (nums1[mid1] > nums2[mid2 - 1]) {
                        return nums1[mid1];
                    } else {
                        return findFirstMedium(nums1, mid1 + 1, r1, nums2, l2, mid2 - 1, r1 - (mid1 + 1) + 1);
                    }
                }

            }
        }
    }
}

算法学自左神!!!!!!!!!

全体起立,致敬左神。(狗头)嘿嘿!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值