力扣算法题:寻找两个正序数组的中位数
解法一:
利用中位数是在一列数字中排中间的那一位,我们便可以一步一步地,两个数组分别进行 ++ 操作,直至遇见中位数。
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
//第二种解法,利用中位数在两个数组中是第 k 小
int len = (nums1.length + nums2.length) ; //两个数组的总长度
Arrays.sort(nums2);
Arrays.sort(nums1);
int left = 0,right = 0;
int astart = 0,bstart = 0;
for(int i = 0;i<=len/2;i++){
left = right;
if(astart < nums1.length && (bstart >= nums2.length || nums1[astart] < nums2[bstart])){
right = nums1[astart++];
}else {
right = nums2[bstart++];
}
}
if(len % 2 == 0){
return ((float)left + (float)right) /2;
}else{
return right;
}
}
}
在这里,left 和 right 的设置正是为了解决数组的总长度是奇偶不同的情况,left 总是记录先前 right 的位置,当数组的长度为偶数的时候,中位数正是 (left + right) /2
同时,当数组a进行 ++ 操作的时候,aStart 的值必须小于a的长度,同时需要a[aStart] 的值小于 b[bStart] 的值,否则的话则bStart++
当然,另一种情况便是b数组的长度已经到头,a数组便可在 i 内进行 ++ 操作,因此:
astart < nums1.length && (bstart >= nums2.length || nums1[astart] < nums2[bstart])
解法一的时间复杂度为 O(m+n)
解法二:
这正是解法一的改版,从一个一个排除升级为一片一片地排除。
设中位数为k, 我们可以每次排除最多 k/2 的长度,这个长度不影响后续操作。
情况有多种:
每次取两个数组的第 k/2 个元素进行比较,把小的一方的前 k/2 个元素悉数排除。
若是 k/2 的长度已经超过其中一方数组的长度,我们则可以弹性选取较小的数组长度作为比较点进行互相比较,同样排除这个比较点大小的元素。
之后便开始递归以上操作直至找到中位数
在每次排除之后,k的值也要相应进行更新,减去已经排除的数量,并且每次都要更新被排除数组的起始位置。
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
//第三种解法,设中位数为第k个,一次排除 k/2 的数量。
int len = (nums1.length + nums2.length) ; //两个数组的总长度
Arrays.sort(nums2);
Arrays.sort(nums1);
//考虑数组总长度奇偶的情况,奇数则两个结果相同,偶数则为中间的两个数,得到的是 第 k 个数而不是索引
int left = (len + 1) / 2;
int right = (len + 2) / 2;
// *0.5 防止其向下取整
return((justDoIt(nums1,0,nums1.length-1,nums2,0,nums2.length-1,left) + justDoIt(nums1,0,nums1.length-1,nums2,0,nums2.length-1,right) )*0.5 );
}
public int justDoIt(int a[],int aStart,int aEnd,int b[],int bStart,int bEnd,int k){
int m = aEnd - aStart+1;
int n = bEnd - bStart+1 ;
//首先保证 a 数组的长度小于 b 数组的长度,这样省事很多。
//当数组的长度为0的时候,就不纳入考虑
if(m > n){
return justDoIt(b,bStart,bEnd,a,aStart,aEnd,k);
}
//case 1 :数组 a 的长度为0,直接不用后面的事
if(m == 0 ){
return b[bStart + k-1];
}
//case 2: 中位数为 第1个(可能为递归后的结果)
if(k == 1){
return Math.min(b[bStart],a[aStart]);
}
//一般 case:
//将数组的长度和 k/2 做对比,若数组长度小于 k/2 的话,则选取数组的长度,舍去的部分也是二者之一
int i = aStart + Math.min((k / 2) ,m) -1;
int j = bStart + Math.min((k / 2) ,n) -1; //由于是索引,所以 -1
//若 a 数组中前 k/2 个数 小于 b 的,则要舍去小的部分
if(a[i] < b[j]){
//此处 a 的开始为 i+1 而非 aStart + i + 1,是因为舍弃的数量加上1 就是 新的起始位置
return justDoIt(a, i +1,aEnd,b,bStart,bEnd,k-( i+1 - aStart ) ); //减去舍去的长度再度二分法
}else{
return justDoIt(a,aStart,aEnd,b,j +1,bEnd,k- (j+1-bStart) ); //b小,舍去b
}
}
}
解法二的时间复杂度为 O(log(m+n))