题目描述
今天做leetcode上的题,遇到了这到题目,自己想了一上午没想到符合时间复杂度要求的解法,所以看了官方解答,但是解答是英文的,看着比较难受,所以整理成中文的,便于以后自己查看和理解。
输入:已经排好序的两个数组nums1和nums2
输出:找出两个数组的中位数,要求时间复杂度为O(log(m+n))。可以假设两个数组都不为空,即不用考虑特殊情况。
示例1:
nums1 = [1, 3]
nums2 = [2]
the median is 2.0
示例2:
nums1 = [1, 2]
nums2 = [3, 4]
the median is (2+3)/2 = 2.5
思路:
为了方便,用A代替nums1,B代替nums2。
假设A有m个数,B有n个数(要保证m<=n)。将A,B各分成两部分,然后将四个部分再合成两个部分,如下效果:
left_part | right_part
A[0], A[1], ..., A[i-1] | A[i], A[i+1], ... ,A[m-1]
B[0], B[1], ..., B[j-1] | B[j], B[j+1], ..., B[n-1]
现在就需要保证满足下面两个条件:
- len(left_part) <= len(right_part)
- max(left_part) <= min(right_part)
那么就可以得到中位数:
median = (max(left_part) + min(right_part)) / 2
因为A,B都是拍了序的数组,因此,要保证前面的两个条件,我们就需要保证:
3. i+j = m-i+n-j(或者m-i+n-j+1),如果n >= m,那么只需要设:i = 0~m, j = (m+n+1) / 2 - i
4. B[j-1] <= A[i]以及 A[i-1] <= B[j]
为什么要保证 n>= m呢?因为这样才能保证j一直非负,而不会出错。
因此,现在要做的就是:
在[0, m]的区间内找到一个i,能够满足条件4。
有两种思路去搜寻i,一种是挨个遍历,另一种就是官方解答所用的二分法。
步骤1:取iMin=0, iMax=m,那么初始i = (0+m) / 2,j=(m+n+1)/2 - i。
开启循环
步骤2:对两个数组所分区间的分段处两边的值进行比较
2.1 当满足条件4时,可以终止循环,输出中位数
2.2
a) 当i位于iMin和iMax之间,并且B[j-1] > A[i]时,说明i偏小,所系需要增加i,因此iMin = i+1;
b) 当i位于iMin和iMax之间,并且B[j] < A[i-1]时,说明i偏大,所以需要减小i,因此iMax = i-1;
2.3 当满足条件的i找到后,求中位数需要先判断m+n是否为奇数:若为奇数,中位数就是max(A[i-1], B[j-1]);若为偶数,中位数就是(max(A[i-1], B[j-1]) + min(A[i], B[j])) / 2。
另外,有几种比较极端的情况需要考虑到:
(1) i = 0,此时 j = n, 并且A[i-1]就不会存在,也就是说A数组中最小的数都比B数组中值最大的数要大,所以中位数就是B[j-1] (m+n为奇数时)或者(A[i]+B[j-1]) / 2 (m+n为偶数);
(2) j = 0,此时i = m,并且B[j-1]不存在,即B数组最小的数比A数组最大的数还要大,所以中位数为A[i-1] (m+n为奇数时)或者(A[i-1]+B[j]) / 2 (m+n为偶数)。
代码如下:
class Solution {
public double findMedianSortedArrays(int[] A, int[] B) {
int m = A.length;
int n = B.length;
// 保证 m <= n
if (m > n) {
int[] temp = A; A = B; B = temp;
int tmp = m; m = n; n = tmp;
}
int iMin = 0, iMax = m, halfLen = (m + n + 1) / 2;
while (iMin <= iMax) {
int i = (iMin + iMax) / 2;
int j = halfLen - i;
if (i < iMax && B[j-1] > A[i]){
// i偏小
iMin = i + 1;
}
else if (i > iMin && A[i-1] > B[j]) {
// i偏大
iMax = i - 1;
}
else {
// 此时i符合条件
int maxLeft = 0;
if (i == 0) { maxLeft = B[j-1]; }
else if (j == 0) { maxLeft = A[i-1]; }
else { maxLeft = Math.max(A[i-1], B[j-1]); }
if ( (m + n) % 2 == 1 ) { return maxLeft; }
int minRight = 0;
if (i == m) { minRight = B[j]; }
else if (j == n) { minRight = A[i]; }
else { minRight = Math.min(B[j], A[i]); }
return (maxLeft + minRight) / 2.0;
}
}
return 0.0;
}
}