今天总结一下前几天写的这一道搜索相关的题目:
自己最开始的思路:
把这两个数组(假设A,B)分割,分割成
A =>
A[0] ~ A[i] A[i] ~ A[n - i] 且 (0 <= i < n, n == A.size() )
B =>
B[0] ~ A[j] A[j] ~ A[n - j] 且 (0 <= j < m, m == B.size() )
那么如何求解 i, j 就是这道题目的关键,观察可得
结论1: A 和 B的前半部分和后半部分的数目相等即
(i - 0 + 1) + (j - 0 + 1) == (n - i - 1) + (m - j - 1)
这样计算的前提是i j 不能为0,于是需要换一种思路
i,j不再表示下表而是前面有几个数字,
可以这么看 : i = 0 表示A中取0个元素, i = n 表示A中取所有的数字(同理可得j)
于是这个关系式为
i + j = n - i + m - j
但是这个地方在处理的时候有一个需要注意的地方
j = (m + n + 1) / 2 - i
- 第一点:n必须小于m,以保证 j 大于等于0,且对小的数组二分会加快计算
- 第二点: 这个 + 1很关键,保证了当m+n为奇数的时候,中位数落在左边,例如 m+n == 7 ,那么中位数在4这个位置,但是如果不加1,i+j为3,中位数会落在右边; 同时m+n为偶数的时候不会影响j的计算
结论2,对i进行二分,当 A[i] < B[j-1] || B[j] < A[i-1] 时说明都没有满足条件,前者说明i小了应该往后移增大, 后者说明i大了应该往前移,且这两个情况不会同时发生,证明很简单,反证法,假设都成立
因为 A[i] < B[j -1] 且 B[j] > B[j -1] 所以 A[i] < B[j]
又因为B[j] < A[j -1] 所以可得A[j] < A[j -1] 与条件矛盾,不成立。
下面就可以coding了, 代码我写的有点冗余没有怎么优化,对于特殊的情况我是分开写的,有些是可以合并的。一会我会对比官网实例code
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
//the size of n1 > the size of n2
vector<int> &n1 = nums1.size() > nums2.size() ? nums1 : nums2;
vector<int> &m1 = nums1.size() > nums2.size() ? nums2 : nums1;
//n,m : size of two vectors
int n = n1.size(), m = m1.size();
//i -> m1, j -> n1
int i = 0, j = 0, k = (n + m + 1) / 2;
int iMin = 0, iMax = m;
/*
** 以上是在做准备为求解代码做准备,下面就可以根据i的取值
** 范围对i进行二分查找了
*/
while(iMin <= iMax){
i = iMin + (iMax - iMin) / 2;
j = k - i;
//特殊情况边界的处理
if(i == 0)
if(m == 0 || m1[i] >= n1[j -1])
break;
else{
iMin = i + 1;
continue;
}
if(i == m)
if( n1[j] >= m1[i-1])
break;
else{
iMax = i - 1;
continue;
}
if(j == n)
if(m1[i] >= n1[j -1])
break;
else{
iMin = i + 1;
continue;
}
if(j == 0)
if(n1[j] >= m1[i - 1])
break;
else{
iMax = i - 1;
continue;
}
//正常情况下的二分
if(m1[i] >= n1[j - 1] && n1[j] >= m1[i - 1]){
break;
}else{
if(m1[i] < n1[j - 1]){
iMin = i+1;
}else{
iMax = i-1;
}
}
}
//下面的代码是在一直i/j的情况下求解中位数,分了两种情况一个是
//偶数一个是奇数,因为奇数的时候是一个数,偶数是两个数,且处理特殊情况(这部分代码可以和上面的合并一些)
double ret = 0;
if( (n + m) % 2 == 0){
int min, max;
if(i != 0 && j != 0)
min = m1[i - 1] > n1[j -1] ? m1[i -1] : n1[j -1];
else{
if(i == 0) min = n1[j - 1];
if(j == 0) min = m1[i - 1];
}
if(i != m && j != n)
max = m1[i] < n1[j] ? m1[i] : n1[j];
else{
if(i == m) max = n1[j];
if(j == n) max = m1[i];
}
ret = (double)(max + min) / 2;
}else{
if(i != 0 && j != 0)
ret = m1[i - 1] > n1[j -1] ? m1[i - 1] : n1[j -1];
else{
if (i == 0) ret = n1[j -1];
if (j == 0) ret = m1[i -1];
}
}
return ret;
}
有时间我会对代码进行优化。
对比官方代码
public double findMedianSortedArrays(int[] A, int[] B) {
int m = A.length;
int n = B.length;
if (m > n) { // to ensure 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;
//这里有j 必不等于 0,因为i的数组是小数组
//如上面说的B[j -1 ] > A[i] 和 A[i-1] >B[j]不会同时成立
//来调整i的位置
//i < iMax & i > iMin只是用来限制相等的情况,如果相等会在下面的情况处理
if (i < iMax && B[j-1] > A[i]){
iMin = i + 1; // i is too small
}
else if (i > iMin && A[i-1] > B[j]) {
iMax = i - 1; // i is too big
}
else { // i is perfect
//求左边最大 和 右边最小
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; }//奇数时直接return
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;
}
总结:
其实这道题目的方法并不是很复杂,就是二分求解,难就难在二分时的计算推导,特别是边界情况的处理,如果一开始分不清,也可以像我一样把所有特殊情况分开处理,虽然冗余,但是在最短时间内可以完成逻辑的实现。就好比数学的分情况讨论,在合并的过程,你不合并也不算错。
参考: https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/xun-zhao-liang-ge-you-xu-shu-zu-de-zhong-wei-shu-b/