在不等长的两个正序数组中找第k大的数
题目:
给定两个大小分别为 n 和
m的正序(从小到大)数组
nums1和
nums2`。请你找出并返回这两个正序数组的 第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;
求它们的上中位数(两个正序数组合并在一起,它们的总长度肯定是偶数,那么就会有两个中位数,上中位数指的是第一个中位数)。
(这里的数字均为在本数组中第几大的数,不是具体数值)
如果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是偶数:
同样先找到各自的上中位数,即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的长度。
还是通过例子介绍:
(都表示是本数组中第几大的值,不表示具体元素数值)
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);
}
}
}
}
}
}
算法学自左神!!!!!!!!!
全体起立,致敬左神。(狗头)嘿嘿!