1,题目要求
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
有两个排序的数组nums1和nums2分别为m和n。
找到两个排序数组的中位数。 总运行时间复杂度应为O(log(m + n))
。
您可以假设nums1和nums2不能都为空。
2,题目思路
对于这道题,要求是找到两个有序数组的中位数。
中位数,顾名思义,就是一个数组中的中间那个数字。如果是一个数组,很好解决,直接根据数组的长度的奇偶关系,判断中位数是中间的数字还是中间两个数字的平均值。
而对于两个数组的情况,如果我们使用普通的遍历的方法,倒也不难,直接遍历即可,类似于双指针法,但是这种方法的时间复杂度为O(m+n)
,不满足题目的要求。
因此,这就要求我们另辟蹊径。
这里,使用Discussion中的1.5k赞同的讲解:
首先,在统计中,中位数用于将集合分成两个长度相等的子集,一个子集中的值总是要大于另一个子集中的值。
如果我们对于数组A,随机取一个位置i,将其分成两部分 :
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~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] |
将left_A和left_B放在一组中,并将right_A和right_B放在另一组中,此时,我们将它们分别命名为:left_part和right_part。
如果在这里,我们可以确保以下两点:
- (1)
len(left_part) == len(right_part)
- (2)
max(left_part) <= min(right_part)
那么,我们就将{A, B}中的所有元素划分成了两个长度相等的部分,并且一部分的值总是大于另外一部分。
此时,中位数 = max(left_part) = min(right_part))/2
而对于上面的两个条件,我们只需要确保:
- (1)
i + j == m - i + n - j (or: m - i + n - j + 1)
- (2)
B[j-1] <= A[i] and A[i-1] <= B[j]
于是,问题就转化为了在[0, m]中搜索一个i,使得:
B[j-1] <= A[i] and A[i-1] <= B[j], ( where j = (m + n + 1)/2 - i )
这里,m<=n,因为我们要保证j为非0的正值,因为0 <= i <= m而且j = (m + n + 1)/2 - i,如果n < m,j有可能为负。
为了降低算法的时间复杂度,我们使用二分搜索,具体步骤如下:
1,设置imin = 0,imax = m,在[imin, imax]内进行搜索
2,设置i = (imin + imax)/2, j = (m+n+1)/2 - i
3,此时,我们有len(left_part)==len(right_part),并且,会出现以下三种情况:
- B[j-1] <= A[i] and A[i-1] <= B[j]
根据上面的阐述,此时我们找到的i就是正确的,停止搜索。 - B[j-1] > A[I]
这意味着A[i]小了,因此我们需要对i进行“调整”,以使得B[j-1] <= A[i]。而调整的方法,就是增加i,增加了i,j也就相对应减少,A[i]增大、B[j-1]减小,B[j-1] <= A[i]说不定就会成立。
此时,我们将搜索的范围设置到[i+1, imax],于是imin = i+1,返回到第2步。 - A[i-1] > B[j]
这意味着A[i-1]大了,于是我们需要减少i,使得A[i-1]<=B[j]。此时,我们的搜索范围是[imin, i-1],于是imax = i-1,返回到第2步。
当i找到时,我们就有中位数:
- 当m+n为奇数时:max(A[i-1], B[j-1])
- 当m+n为偶数时:(max(A[i-1], B[j-1]) + min(A[i], B[j]))/2
然而,如果对于边界情况: i=0,i=m,j=0,j=n,此时A[i-1],B[j-1],A[i],B[j] 可能不存在。
如果A[i-1],B[j-1],A[i],B[j] 其中有的不存在,我们就不必去检查条件了。比如,如果i = 0, 那么A[i-1]一定不存在,我们也就不必检查A[i-1]<=B[j]了。
于是,对于之前的步骤:
我们在[0, m]中搜索i,使得:
(j == 0 or i == m or B[j-1] <= A[i]) and
(i == 0 or j == n or A[i-1] <= B[j])
where j = (m + n + 1)/2 - i
而在搜索的过程中,我们有有以下三种情况:
(j == 0 or i == m or B[j-1] <= A[i]) and (i == 0 or j = n or A[i-1] <= B[j])
此时i是完美的,停止搜索j > 0 and i < m and B[j - 1] > A[i]
此时i过小,需要增加i。i > 0 and j < n and A[i - 1] > B[j]
此时i过大,需要减少i。
3,代码实现
static const auto s = []() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
return nullptr;
}();
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int m = nums1.size(), n = nums2.size(), l = 0, r = m;
if(m > n)
return findMedianSortedArrays(nums2, nums1); //这点不是递归!只是为了保证m <= n
while(l <= r){
int i = (l + r)/2, j = (m+n+1)/2 -i;
if(i!=0 && nums1[i-1] > nums2[j])
r = i-1;
else if(i < m && nums2[j-1] > nums1[i])
l = i+1;
else{
int lmax = !i ? nums2[j - 1] : (!j ? nums1[i - 1] : max(nums1[i - 1], nums2[j - 1]));
if((m+n)%2)
return lmax;
else{
int rmin = i == m ? nums2[j] : (j == n ? nums1[i] : min(nums1[i], nums2[j]));
return 0.5 * (lmax + rmin);
}
}
}
return 0.0;
}
};