二分查找两正序数组中第k小的数

给定两个不同时为空的正序数组:nums1,nums2

求这两数组中第k小的数

如:

nums1=[2,4,5,7,9];

nums2=[1,3,6,8,10,11,12];

k=7;

第k小的数为:7

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

采用二分查找,时间复杂度为:O(lg(m+n)):

一、需要用到的技巧:

  1. 递归

  2. 数组的逻辑删除(为数组设置头指针,执行程序时只访问其头指针之后的元素)

    如:

    对于数组 nums=[8,31,453,11,634,24,-1,21]

    为其设置头指针h可以实现逻辑删除其前h个元素

    例如,当h=3时,程序从3开始遍历数组,可以访问到的元素为[11,634,24,-1,21]

  3. 二分查找

二、算法的内容

  1. 首先判断两个数组是否为空。因为两数组不同时为空,所以当有其中一个为空时,有用的数组只有一个,那么这个问题就变成了:在一个正序数组中找出第k小的数

    因为是有序数组,所以第k小的数当然就是当前数组的第k个啦!

  2. 再判断k是否为1。如果k=1,意思就是从当前两个有序数组中找到(第一个)最小的数。那么这个数要么是nums1的第一个数,要么是nums2的第一个数,我们此时将这两个数比较一下就是找到第1小的数了。

  3. 如果以上两种条件都不满足,那就是一般的情况了。

    即:从两个不为空的正序数组中找出第k(k≠1)小的数。

    我们知道,这k个数必然从nums1、nums2的最前面取。

    但我们不知道,nums1、nums2各取多少个。

    所以我们干脆先将k平分,让nums1、nums2各取k/2个。

    随后,我们对nums1、nums2的前k/2数进行比较,

    如果nums1的第k/2个数小于nums2的第k/2个数,那么我们可以肯定:

    nums1中第k/2个数是这两个数组中的第k小的数,所以第k小的数一定不在这个数的前面

    所以我们可以把nums1的前k/2个数删掉。

    (如果nums2的第k/2个数小于nums1的第k/2个数,操作也是类似)

    然后再从新的两个数组中寻找前k-k/2小的数

    (因为已经找出了k/2个最小的数,再找k-k/2就行了)

    重复此操作,直到k=1时,我们可以得到想要的数。

三、代码实现


public class A {
    public static void main(String[] args) {
        int[] nums1 = {2,4,5,7,9};
        int[] nums2 = {1,3,6,8,10,11,12};
        int k = 12;
        int res = f(nums1, nums2,k);
        System.out.println(res);
    }

    /**
     * 这个方法是用来设置缺省头指针(刚开始为0)和做一些输入数据的异常处理的
     * 和算法内容无关
     */
    static int f(int[] nums1, int[] nums2, int k){
        if(nums1.length == 0 && nums2.length == 0) throw new RuntimeException("两数组不能同时为空");
        if(k > nums1.length+nums2.length) throw new RuntimeException("k不能超出两数组的长度之和");
        return f(nums1, nums2, 0, 0, k);
    }

    /**
     * 查找两个有序数组中第k小的数
     * @param nums1 数组1
     * @param nums2 数组2
     * @param h1 数组1的头指针
     * @param h2 数组2的头指针
     * @param k k
     */
    static int f(int[] nums1, int[] nums2, int h1, int h2, int k){
        /*
           注意:
                1.k是表示第几小,如果是第1小,那么下标是0的,所以下面有k的下标都得-1
                2.这里用位运算“k>>1”来计算“k/2”
         */
        

        //第一
        if(h1 >= nums1.length){
            //nums1为空,从nums2里找第k小的数
            //h2是偏移值,如果不加就是从原来的nums2的数组中选了
            //k-1表示选取第k个数
            return nums2[h2 + k - 1];
        }
        if(h2 >= nums2.length){
            //nums2为空,从nums1里找第k小的数
            //h1是偏移值,如果不加就是从原来的nums1的数组中选了
            //k-1表示选取第k个数
            return nums1[h1 + k - 1];
        }


        //第二
        if(k == 1){
            //k为1,输出当前最小的数
            return Math.min(nums1[h1], nums2[h2]);
        }


        //第三
        //计算k的一半
        //注意:为了防止指针溢出,当k/2大于当前数组的长度时,我们取最大的长度作为值
        int m1 = Math.min(k>>1, nums1.length - h1);
        int m2 = Math.min(k>>1, nums2.length - h2);

        //判断谁大,然后将新的数组进行递归
        //这里的h1和h2也一样是偏移值,如果不加就是在原来的数组中比较了
        if(nums1[h1 + m1 - 1] < nums2[h2 + m2 - 1]){
            //删除nums1前m1个数,即h1指针再后移m1位
            //同时k减去已经删除的前m1小数的个数
            return f(nums1, nums2, h1 + m1, h2, k - m1);
        }else{
            //删除nums2前m2个数,即h2指针再后移m2位
            //同时k减去已经删除的前m2小数的个数
            return f(nums1, nums2, h1, h2 + m2, k - m2);
        }
    }
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值