问题描述
There are two sorted arrays nums1 and nums2 of size m and n respectively.
Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)).
You may assume nums1 and nums2 cannot be both empty.
Example 1:
nums1 = [1, 3]
nums2 = [2]
The median is 2.0
Example 2:
nums1 = [1, 2]
nums2 = [3, 4]
The median is (2 + 3)/2 = 2.5
大概意思是输入两个有序的数组,给出两个数组合并后的中位数。题目本身不难,但是加上了限制之后就没什么头绪了,要求的时间复杂度是O(log(m+n)). 看来要用二分法,但是怎么去做还是不大容易想到的。
解题思路
brute force
看到题目一开始第一想到的就是brute force,直接构造一个m+n长度的数组然后走一遍两个数组按照大小存起来然后直接找,但是后面规定了时间复杂度要在O(log(m+n)),所以并不能用
二分查找+数学
想了很久没头绪,看了solution,也讲得很晕乎,大概明白了解题思路但是很多细节处理没说明白,就又去google了一下具体的解法,找到一篇很不错的文章讲这个题(如何解这题),大家有兴趣的可以去看一下讲得很细节。懒得看的也可以直接看我下面的解法。大概思想是不用管median前面具体时间什么内容,目标只是找到两个数组中分别的分界线,是的左边元素和右边元素数量以及值都能够满足median的定义即可。即:
- 将数组A按照下标i分割后得到A的左右边
left_A | right_A
A[0], A[1], ..., A[i-1] | A[i], A[i+1], ..., A[m-1]
- 需要满足median数量条件,因此B的分割j的值可以根据i的值来定。即:
i + j = (m+n+1) / 2
其中m和n分别代表数组A,B的长度, i, j代表A,B数组中分别贡献到median左边的元素数量(这里很重要,之前很多次解这题写不出解法就是没有掌握这一点)。
- 理解了上面的条件之后,就可以用二分法来解这个题了,用iMin和iMax来表示数组A贡献给左边数组的最小和最大值。每次判断是否满足中位数的数值关系, 即:
max(left_part) ≤ min(right_part)
如果A[i-1] > B[j]
说明i的值太大了需要变小(即 iMax = middle - 1
)
如果A[i] < B[j-1]
说明i的值太小了需要变大(即 iMin = middle + 1
)
- 当上面的数值关系被满足之后需要判断几个特殊情况:
1. 两个数组是否存在贡献0个元素和贡献所有元素到左边
2. 两个数组的和是否是奇数
具体的思路写在代码里面了。注意, 当两个数组和是偶数时需要找出左边和右边分别对应的最大值和最小值然后计算中位数。
代码实现
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length, n = nums2.length;
if(m > n) { //switch nums1 and nums2 if m > n
int[] temp = nums1;
nums1 = nums2;
nums2 = temp;
int tmp = m;
m = n;
n = tmp;
}
int iMin = 0, iMax = m, halflength = (m + n + 1) / 2;
while(iMin <= iMax) {
int middle = (iMin+iMax) / 2;
int middle2 = halflength - middle;
//iMin指的是nums1数组贡献的最小数,iMax指的是最大数
if(middle > 0 && nums1[middle - 1] > nums2[middle2]) {
iMax = middle - 1;
} else if(middle < m && nums1[middle] < nums2[middle2 - 1]) {
iMin = middle + 1;
} else { //此时已经找到了middle的位置, middle的值代表着nums1贡献给左半部分的数量
int lefthalfend;
if(middle == 0) { //nums1中没有任何元素在中位数左边
lefthalfend = nums2[middle2 - 1];
} else {
if(middle2 == 0) {//nums2中没有任何元素在中位数左边
lefthalfend = nums1[middle - 1];
} else { //两个数组都至少贡献一个元素在中位数左边
lefthalfend = Math.max(nums1[middle - 1], nums2[middle2 - 1]);
}
}
if((m+n) % 2 != 0 )
return lefthalfend;
else {
int righthalfend;
if(middle == m) { //nums1中没有任何元素在中位数右边
righthalfend = nums2[middle2];
} else {
if(middle2 == n) {//nums2中没有任何元素在中位数右边
righthalfend = nums1[middle];
} else { //两个数组都至少贡献一个元素在中位数左边
righthalfend = Math.min(nums1[middle], nums2[middle2]);
}
}
return (lefthalfend + righthalfend) / 2.0;
}
}
}
return 0.0;
}
结果:
复杂度分析
经过复杂的数学推导(个人感觉这题更像个数学题),程序的时间复杂度终于达到了题目要求的O(log(m+n)), 空间复杂度只有O(1).
分析
果然,评论区大佬们对这题意见很大hhh
今天是第二天,遇到了第一个hard的题目,果然很不容易,希望后面能够熟练一点,当然遇到这样的题目想不到这种思路也很正常,这题之前也见过几次,不过之前看了答案光顾着惊呼“牛逼,牛逼”了,按照答案的思路写完然后细节处理直接照着答案写了觉得也差不多。今天再遇到才发现这么弄根本不行,觉得这题细节处理才是更重要的部分,毕竟这题目本身的探讨价值emmm…不是很高。希望这次仔细理解了之后下次见到能够完美的处理细节部分~