声明:解答的原文来自于https://leetcode.com/problems/median-of-two-sorted-arrays/solution/,仅仅翻译和加上了自己的理解。
题目
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)).
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
分析:
根据我的理解,这个题目是要找到一个割点,这个点可以再数组里面,也可以不在,如果比这个点小的在左边,比这个点大的在右边,则左右数组的个数一样多。
中点的作用在于把一个集合划分为两个等长集合,其中一个集合的元素总是小于另一个集合。如果我们明白了中点的作用,我们就很接近答案了。
首先,我们随便用一个位置下标i把A分成两部分:
left_A | right_A
A[0], A[1], ..., A[i-1] | A[i], A[i+1], ..., A[m-1]
由于A有m个元素,就有m+1种划分方法(i=0,1,2,...,m),而且如果 len(left_A) = i, 那么len(right_A) = m - i .
其中当 i = 0 , left_A为空,当 i = m , right_A 为空。
同理,任意位置下标j也可以把B划分成两部分:
left_B | right_B
B[0], B[1], ..., B[j-1] | B[j], B[j+1], ..., B[n-1]
此时,len(left_B)=j,len(right_B)=n-j
令left_part=left_A + left_B,
right_part=right_A + right_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]
如果能够满足:
1) len(left_part) == len(right_part)
2) max(left_part) <= min(right_part)
那么我们实际上已经把A+B划分成了两个等长的集合,且一个集合的任意元素总是小于另一个集合的任意元素。此时,中点=(max(left_part) + min(right_part))/2(中间数就应该等于左边最小的和右边最大的数的平均数)
为了使得集合left_part和right_part满足:
1) len(left_part) == len(right_part)
2) max(left_part) <= min(right_part)
需要以下条件:
(1) 如果len(left_part) == len(right_part),
根据len(left_part)=len(left_A)+len(left_B)=i+j,
len(right_part)=len(right_A)+len(right_B)=(m-i)+(n-j)
需要满足i + j == m - i + n - j
如果满足i + j = m - i + n - j,那么,j与i满足
j = (m + n + 1)/2 - i,i = 0 ~ m,(m + n + 1是为了当m+n为奇数时j是个整数有意义)
当n >= m时,我们只去寻找这样的i就可以,反之,寻找j。
i = 0 ~ m, j = (m + n + 1)/2 - i
(2) 如果max(left_part) <= min(right_part),那么
根据A,B均为有序数组可知
max(left_part)=max{A[i-1],B[j-1]},
max(right_part)=max{A[i],B[j]}
如果max(left_part) <= min(right_part),那么
1.A[i-1]<A[i],由A有序,显然
2.A[i-1]<B[j]
3.B[j-1]<B[j],由B有序,显然
4.B[j-1]<A[i]
实际需要满足的条件是B[j-1] <= A[i] 和 A[i-1] <= B[j]
1.我们先考虑A[i-1],B[j-1],A[i],B[j]都存在的情况,对于i=0/i=m/j=0/j=n 的特殊情况可以稍后考虑
2.由于0 <= i <= m , j = (m + n + 1)/2 - i,为了保证j非负数(下标非负数)我们需要保证n >= m,如果 n < m ,当i取到m时,j可能是个负数。
因此,在n>=m的情况下,我们去A中寻找i,使得i满足
i在[0, m]中取值,j = (m + n + 1)/2 - i ,且
B[j-1] <= A[i] 和 A[i-1] <= B[j] 成立。
那么可以做二分查找:
<1> 令 imin = 0, imax = m,搜索范围定义为[imin, imax]
<2> 令 i = (imin + imax)/2, j = (m + n + 1)/2 - i
(根据上面len(left_part) == len(right_part)的分析,j = (m + n + 1)/2 - i时能够保证len(left_part)==len(right_part) )
<3> 当:
<a> B[j-1] <= A[i] and A[i-1] <= B[j]
表明我们找到了这样的i,停止搜索
<b> B[j-1] > A[i]
这表示A[i]太小,B[j-1]过大,所以应该让A[i]增大,即i下标右移A[i]变大,此时由j = (m + n + 1)/2 - i会使得j左移B[j-1]变小
即满足条件的i应该会在[i+1, imax]范围内,令imin = i+1, 跳转到<2>.
<c> A[i-1] > B[j]
这表示A[i]太大,B[j-1]过小,所以应该让A[i]变小,即i下标左移A[i]变小,此时由j = (m + n + 1)/2 - i会使得j右移B[j-1]变大
即满足条件的i应该会在[imin, i-1]范围内,令imin = i-1, 跳转到<2>.
当搜索到了i时,中点应该是:
max(A[i-1], B[j-1]) (当m + n是个奇数时,中点是中间那个数)
或者(max(A[i-1], B[j-1]) + min(A[i], B[j]))/2 (当m+n是个偶数时,中点是中间两个数的平均数)
我们现在来考虑一下特殊情况:
考虑A[i-1],B[j-1],A[i],B[j]都存在的情况,对于i=0/i=m/j=0/j=n
我们需要保证max(left_part) <= min(right_part)
如果A[i-1],B[j-1],A[i],B[j]都存在的情况,我们需要保证B[j-1] <= A[i] 和 A[i-1] <= B[j].
如果A[i-1],B[j-1],A[i],B[j]中某个不存在,我们不需要检查某些条件了。
1.如果i=0,那么A[i-1]不存在,不需要检查A[i-1] <= B[j]是否满足,
同理,j=0你们不需要检查B[j-1] <= A[i]是否满足,那么检查的条件可以如下(利用判断条件短路原则)
(j == 0 or i == m or B[j-1] <= A[i]) &&
(i == 0 or j == n or A[i-1] <= B[j])
循环过程中,我们只会遇到三种情况:
<a> (j == 0 or i == m or B[j-1] <= A[i]) and
(i == 0 or j = n or A[i-1] <= B[j])
找到了相关的i,停止搜索
<b> j > 0 and i < m and B[j - 1] > A[i]
i太小,应该i++
<c> i > 0 and j < n and A[i - 1] > B[j]
i太大,应该i--
注意到i < m ==> j > 0 and i > 0 ==> j < n . Because:
m <= n, i < m 的情况下, j = (m+n+1)/2 - i > (m+n+1)/2 - m >= (2*m+1)/2 - m >= 0
m <= n, i > 0 的情况下, j = (m+n+1)/2 - i < (m+n+1)/2 <= (2*n+1)/2 <= n
因此,<b> 和 <c>中我们需要检查j > 0 和 j < n.
因此,代码为:
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int m = nums1.size();
int n = nums2.size();
if (m>n)
{
return findMedianSortedArrays(nums2, nums1);
}
int imin = 0,
imax = m,
half_len = (m + n + 1) / 2;
int max_left;
int min_right;
while (imin<=imax)
{
int i = (imin + imax) / 2;
int j = half_len - i;
if ((i<m)&&(nums2[j-1]>nums1[i]))
{
imin = i + 1;
}else if ((i>0)&&(nums1[i-1]>nums2[j]))
{
imax = i - 1;
}
else
{
if (i==0)
{
max_left= nums2[j - 1];
}else if (j==0)
{
max_left = nums1[i - 1];
}
else
{
max_left = max(nums1[i - 1], nums2[j - 1]);
}
if ((m+n)%2==1)
{
return max_left;
}
if (i==m)
{
min_right = nums2[j];
}else if (j==n)
{
min_right = nums1[i];
}
else
{
min_right = min(nums1[i], nums2[j]);
}
return (max_left + min_right) / 2.0;
}
}
}
};
时间复杂度:
O(log(min(m,n)))
首先,搜索范围是[0,m],m<=n,根据二分搜索特性,搜索长度每次减半,只需要log(m)次循环还有一些常数操作,因此时间复杂度为O(log(m))
空间复杂度:
O(1)
仅仅需要保存9个局部变量,因此空间复杂度为O(1).
附:关于割点的方法,该文章讲述的更成系统:
http://blog.csdn.net/hk2291976/article/details/51107778