给定两个不同时为空的正序数组: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)):
一、需要用到的技巧:
-
递归
-
数组的逻辑删除(为数组设置头指针,执行程序时只访问其头指针之后的元素)
如:
对于数组 nums=[8,31,453,11,634,24,-1,21]
为其设置头指针h可以实现逻辑删除其前h个元素
例如,当h=3时,程序从3开始遍历数组,可以访问到的元素为[11,634,24,-1,21]
-
二分查找
二、算法的内容
-
首先判断两个数组是否为空。因为两数组不同时为空,所以当有其中一个为空时,有用的数组只有一个,那么这个问题就变成了:在一个正序数组中找出第k小的数。
因为是有序数组,所以第k小的数当然就是当前数组的第k个啦!
-
再判断k是否为1。如果k=1,意思就是从当前两个有序数组中找到(第一个)最小的数。那么这个数要么是nums1的第一个数,要么是nums2的第一个数,我们此时将这两个数比较一下就是找到第1小的数了。
-
如果以上两种条件都不满足,那就是一般的情况了。
即:从两个不为空的正序数组中找出第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);
}
}
}