地址:
https://leetcode.com/problems/median-of-two-sorted-arrays/description/
类别: Binary Search
难度: Hard
描述:
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
给定两个长度分别为m和n且排好序(增序)的数组nums1和nums2,求出这两个数组合并后的中位数。
在这道题的讨论区大致有两种思路,一种是将此问题归为求第K小数问题,另一种则是从中位数的定义出发求解。
原讨论地址:
https://discuss.leetcode.com/topic/5728/share-one-divide-and-conquer-o-log-m-n-method-with-clear-description
已知len(nums1)= m, len(nums2) = n,我们最终要求的中位数即它们合并后的第(m+n)/2小的数(奇偶情况在代码中讨论)。
比较nums1[m/2] 与 nums2[n/2], 若nums1[m/2] (m/2 + 1 + n/2), nums2保持不变,舍弃nums1中点nums1[m/2]及其之前的元素(因为这些元素都不可能是第k小,即比第k小要小),
然后继续在nums2和删减后的nums1中寻找第 k - (m/2 + 1)小的数。
时间复杂度为O(log(m+n))。
代码实现
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int size1 = nums1.size(), size2 = nums2.size();
int total = size1 + size2;
/** use double 2.0 to devide ! if interger, will wrong **/
return total & 1 ? findKth(nums1, nums2, 0, size1, 0, size2, total/2+1) :
(findKth(nums1, nums2, 0, size1, 0, size2, total/2 + 1) + findKth(nums1, nums2, 0, size1, 0, size2, total/2))/2.0;
}
/** k : k-the element refers to the index (k-1) base index**/
/** e1 and e2 is open field **/
int findKth(vector<int>& nums1, vector<int>& nums2, int s1, int e1, int s2, int e2, int k) {
/** e1-s1 + e2-s2 > k **/
if(e1 - s1 > e2 - s2) return findKth(nums2, nums1, s2, e2, s1, e1, k);
if(s1 == e1) return nums2[s2+k-1];
if(k==1) return min(nums1[s1], nums2[s2]);
int len1 = min(e1-s1, k/2);
int len2 = k - len1;
int m1 = s1 + len1 - 1;
int m2 = s2 + len2 - 1;
if(nums1[m1] < nums2[m2]) {
return findKth(nums1, nums2, s1 + len1, e1, s2, s2 + len2, k - len1);
}
else if(nums1[m1] > nums2[m2]) {
return findKth(nums1, nums2, s1, s1 + len1, s2 + len2, e2, k - len2);
}
else {
return nums1[m1];
}
}
};
原讨论地址:
https://discuss.leetcode.com/topic/4996/share-my-o-log-min-m-n-solution-with-explanation
(原帖讨论很详细清晰,有条件建议看原贴)
已知len(nums1)= m, len(nums2) = n,我们最终要求的中位数的意义在于将 合并后的数组划分成长度相等的两个子集,且一个子集的所有元素都大于或等于另一个子集的所有元素。
首先我们考虑在nums1的随机位置将其划分为两部分:
left_A | right_A
A[0], A[1], ..., A[i-1] | A[i], A[i+1], ..., A[m-1]
因为nums1共有m个元素,所以有m+1( i = 0 ~ m )种切法,且len(left_A) = i, len(right_A) = m - i 。
注意:当i = 0时,left_A为空;当i = m时,right_A为空。
与之类似,在随机位置j将nums2分成两部分:
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:
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)
则我们将{nums1,nums2}中的所有元素分成两个等长的部分,一部分总是大于另一部分,此时不难得到median =(max(left_part)+ min(right_part))/ 2。
将上面的条件进一步细化有:
(1) i + j == m - i + n - j (or: m - i + n - j + 1)
若 n>=m, 则当在nums1的划分位置确定后,可以得到nums2的位置:i = 0 ~ m, j = (m + n + 1)/2 - i(n>=m保证j不为负)
(2) B[j-1] <= A[i] and A[i-1] <= B[j]
在这里我们已经将原问题转化为寻求较小数组的合适划分位置使最后得到的划分满足条件,而在寻求这个位置的过程中,我们可以利用二分查找,所以最后的时间复杂度为O(log(min(m,n)) 。
二分查找过程:
(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);
此时有三种情况:
(a)B[j-1] <= A[i] and A[i-1] <= B[j]
即找到了合适划分位置i,停止搜索。
(b)B[j-1] > A[i]
说明A[i]太小,必须增大i以得到“B [j-1] <= A [i]”(因为当i增加时,j会减少,即A[i]增大,B[j-1]减小)。
置imin = i+1,搜索范围变为[i+1, imax],回到步骤(2);
(c)A[i-1] > B[j]
说明A[i-1]太大,必须减小i以得到“A[i-1]<=B[j]”。
置imax = i-1,搜索范围变为[imin, i-1],回到步骤(2);
加上边界条件后如下:
(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 < m 可以推出 j > 0)
i > 0 and j < n and A[i - 1] > B[j]
需要减小i(注意i > 0 可以推出 j < n)
最后找到合适的位置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
还有另一个讨论思路也与之相同,不过在处理奇偶和边界条件时用到了很巧妙的方法,地址:
https://discuss.leetcode.com/topic/16797/very-concise-o-log-min-m-n-iterative-solution-with-detailed-explanation
代码实现
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int length1 = nums1.size(), length2 = nums2.size();
if(length1 > length2) return findMedianSortedArrays(nums2, nums1);
int imin = 0, imax = length1, halfTotalLength = (length1 + length2 + 1) >> 1;
int i, j, max_of_left, min_of_right;
while(imin <= imax) {
i = (imin + imax) >> 1;
j = halfTotalLength - i;
if(i < length1 && 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_of_left = nums2[j-1];
else if(j == 0) max_of_left = nums1[i-1];
else max_of_left = max(nums1[i-1], nums2[j-1]);
if((length1+length2)%2==1) return max_of_left;
if(i == length1) min_of_right = nums2[j];
else if(j == length2) min_of_right = nums1[i];
else min_of_right = min(nums1[i], nums2[j]);
return (max_of_left + min_of_right) / 2.0;
}
}
}
};