题目描述(题目难度,困难)
给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。
请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。
示例 1:
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5
示例代码
下面是我时间复杂度为 O(log(min(m,n))) 的 Java 代码,代码虽然看起来比较长,但其实大部分是重复逻辑(^&^)。
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
//因为题目说两数组不会同时为空,所以这种情况忽略
//有一个数组为空的情况
if(nums1 == null || nums1.length == 0)
if(nums2.length % 2 == 0) return (nums2[nums2.length/2-1]+nums2[nums2.length/2])/2.0;
else return nums2[nums2.length/2];
if(nums2 == null || nums2.length == 0)
if(nums1.length % 2 == 0) return (nums1[nums1.length/2-1]+nums1[nums1.length/2])/2.0;
else return nums1[nums1.length/2];
//两数组不存在交叉
int len = nums1.length + nums2.length;//总长度
int m = len/2;
if(nums1[nums1.length-1] <= nums2[0]){
if(len % 2 == 0){
if(m-1 > nums1.length-1) return (nums2[m-nums1.length-1]+nums2[m-nums1.length])/2.0;
else if(m <= nums1.length-1) return (nums1[m-1]+nums1[m])/2.0;
else return (nums1[m-1]+nums2[m-nums1.length])/2.0;
}else{
if(m > nums1.length-1) return nums2[m-nums1.length];
else return nums1[m];
}
}
if(nums2[nums2.length-1] <= nums1[0]){
if(len % 2 == 0){
if(m-1 > nums2.length-1) return (nums1[m-nums2.length-1]+nums1[m-nums2.length])/2.0;
else if(m <= nums2.length-1) return (nums2[m-1]+nums2[m])/2.0;
else return (nums2[m-1]+nums1[m-nums1.length])/2.0;
}else{
if(m > nums2.length-1) return nums1[m-nums2.length];
else return nums2[m];
}
}
//两数组存在交叉,使用类二分查找
int i0 = 0, j0 = 0;//i,j 分别为两数组搜索区间的起始下标
int len1 = nums1.length, len2 = nums2.length;//len1,len2 分别表示两数组搜索区间的长度
int i, j,L;
if(len % 2 == 0){//两个值m-1,m
while(true){
i = i0 + len1/2;
j = j0 + len2/2;
L = i + j +1;
if(nums1[i] < nums2[j]){//nums2[j]为大值
if(L > m){//大值指针前移
if((len2=j-j0) == 0) return (nums1[i+m-L]+nums1[i+m-L+1])/2.0;
}else{
if(L == m && (i >= nums1.length-1 || nums1[i+1] >= nums2[j])){
if(j <= 0 || nums1[i] > nums2[j-1]) return (nums1[i]+nums2[j])/2.0;
else return (nums2[j-1]+nums2[j])/2.0;
}
//小值指针后移
if((len1 -= i-i0+1) == 0) return (nums2[j+m-L]+nums2[j+m-L-1])/2.0;
i0 = i+1;
}
}else{//nums1[i]为大值
if(L > m){
if((len1 = i-i0) == 0) return (nums2[j+m-L]+nums2[j+m-L+1])/2.0;
}else{
if(L == m && (j >= nums2.length-1 || nums2[j+1] >= nums1[i])){
if(i <= 0 || nums2[j] > nums1[i-1]) return (nums1[i]+nums2[j])/2.0;
else return (nums1[i-1]+nums1[i])/2.0;
}
if((len2 -= j-j0+1) == 0) return (nums1[i+m-L]+nums1[i+m-L-1])/2.0;
j0 = j+1;
}
}
}
}else{//只有一个值
while(true){
i = i0 + len1/2;
j = j0 + len2/2;
L = i + j +1;
if(nums1[i] < nums2[j]){//nums2[j]为大值
if(L > m){//大值指针前移
if((len2 = j-j0) == 0) return nums1[i+m-L+1];
}else{
if(L == m && (i >= nums1.length-1 || nums1[i+1] >= nums2[j])) return nums2[j];
//小值指针后移
if((len1 -= i-i0+1) == 0) return nums2[j+m-L];
i0 = i+1;
}
}else{//nums1[i]为大值
if(L > m){
if((len1 = i-i0) == 0) return nums2[j+m-L+1];
}else{
if(L == m && (j >= nums2.length-1 || nums2[j+1] >= nums1[i])) return nums1[i];
if((len2 -= j-j0+1) == 0) return nums1[i+m-L];
j0 = j+1;
}
}
}
}
}
}
思路解析
首先约定符号,假设 a,b 分别为两数组 A,B 的元素,i,j 为 a,b 在各自数组的下标。
整个有序序列(两数组归并后),记为 M,先假设 M.length 为奇数
假设 a 在 M 中的实际下标为 la,那么所有小于等于 la 的下标值我们可以记为 La。这个La,我们称其为 a 在 M 中的下标下界
在 M 中的中位数下标为 target,即我们要的就是 M[target]
对于这个题目存在下述两个事实:
假设 a = max(a, b),则 La = i+j+1(注意如果 B[j+1] >= a,则la == La;如果B[j+1] < a,则la >= La)
如果 La > target,则在 A 数组中,a 后面的所有元素包括 a 都不可能是M[target]。
如果 La <= target,则在 B 数组中,b 前面的所有元素包括 b 都不可能是 M[target](这个难理解一点,给出证明)。
关于第二点第二条的证明如下:
因为 B 数组中 b 前面的元素只有 j 个,A 数组中在 b 前面的元素最多有 i 个,所以 b 在 M 中的下标最大只能是 i+j。
即 lb <= i+j < i+j+1 = La,即 lb < La
又 La <= target,所以 lb < target,b 都不可能了,b 前面的元素更不可能了。
所以当 La > target,指向 a 的指针前移。当 La <= target,指向 b 的指针后移。
那么我们可以设计一个类似二分查找的算法,移动指针的时候每次移动待搜索区间的一半。