题目描述:
一个最简单、最容易想到的方法是:将两个数组排序、存入同一个vector,计算中位数,如:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size();
int m = nums2.size();
vector<int> nums(n + m);
//int* nums = new int[n + m];
if (n == 0) {
if (m % 2)return nums2[m / 2];
else return (nums2[m / 2 - 1] + nums2[m / 2]) / 2.0;
}
if (m == 0) {
if (n % 2)return nums1[n / 2];
else return (nums1[n / 2 - 1] + nums1[n / 2]) / 2.0;
}
int count = 0, i = 0, j = 0;
while (count < (n + m)) {
if (i == n) {
while (j < m) {
nums[count++] = nums2[j++];
}
break;
}
if (j == m) {
while (i < n) {
nums[count++] = nums1[i++];
}
break;
}
if (nums1[i] < nums2[j])nums[count++] = nums1[i++];
else nums[count++] = nums2[j++];
}
if (count % 2)return nums[count / 2];
else return (nums[count / 2 - 1] + nums[count / 2]) / 2.0;
}
复杂度分析:遍历两个数组,时间复杂度为O(m+n);创建了一个数组,空间复杂度为O(m+n)。题目要求时间复杂度为O(log(m+n)),显然此方法不符合要求。
关于上述方法,实际上不需要排序、存储两数组,对于m+n为奇数的,只需要知道两数组中第(m+n)/2+1小的元素就可以了;偶数时,知道第(m+n)/2和第(m+n)/2+1个元素就可以了。所以,有:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size();
int m = nums2.size();
int len = n + m, l = -1, r = -1, idxn = 0, idxm = 0;
for (int i = 0; i <= len / 2; i++) {
l = r;
if (idxn < n && (idxm >= m || nums1[idxn] < nums2[idxm]))
r = nums1[idxn++];
else
r = nums2[idxm++];
}
if (len % 2)return r;
else return (l + r) / 2.0;
}
此时,由于需要遍历前(m+n)/2个元素,时间复杂度为O(m+n);没有创建新的数组,空间复杂度为O(1)。显然,时间复杂度仍不满足题目要求。
题解:
一、二分法
观察题目要求的时间复杂度O(log(m+n)),看到log第一时间想到二分法。
假设我们要找第k小的元素,分别在两数组中找到第k/2个元素,比较当前两元素,小的那个元素所在的数组的前k/2个元素一定不是要找的,故排除;此时,令k=k-k/2,问题转化为,寻找第k(=k-k/2)小的元素,反复应用上面思路,直至k=1,则问题解决。
一个特例,假设其中一个数组的长度小于k/2,只需要将这个数组的最后一个元素拿出来比较就可以了。
奇偶数?奇数时:寻找第(m+n)/2+1小的元素;偶数时,寻找第(m+n)/2和第(m+n)/2+1小的元素。
递归实现
double findMedianSortedArraysDiv(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size();
int m = nums2.size();
int len = n + m;
if (len % 2)
return getKth(nums1, 0, n - 1, nums2, 0, m - 1, len / 2 + 1); //Kth
else
return (getKth(nums1, 0, n - 1, nums2, 0, m - 1, len / 2) + getKth(nums1, 0, n - 1, nums2, 0, m - 1, len / 2 + 1)) / 2.0;
}
int getKth(vector<int>& nums1, int s1, int e1, vector<int>& nums2, int s2, int e2, int k) {
int len1 = e1 - s1 + 1;
int len2 = e2 - s2 + 1;
if (len1 > len2)return getKth(nums2, s2, e2, nums1, s1, e1, k);
if (!len1)return nums2[s2 + k - 1];
if (k == 1)return nums1[s1] < nums2[s2] ? nums1[s1] : nums2[s2];
int k1 = len1 < k / 2 ? len1 : k / 2;
int k2 = len2 < k / 2 ? len2 : k / 2;
int p1 = s1 + k1 - 1;
int p2 = s2 + k2 - 1;
if (nums1[p1] < nums2[p2])
return getKth(nums1, p1 + 1, e1, nums2, s2, e2, k - k1);
else
return getKth(nums1, s1, e1, nums2, p2 + 1, e2, k - k2);
}
复杂度分析:原问题规模为k=(m+n)/2+1,每排除一次,规模减少k/2。故2^x=k,需要x=logk=log(m+n/2)次循环,时间复杂度为O(log(m+n));由于递归为尾递归,编译器没有额外的堆栈操作,空间复杂度为O(1)。
二、“割”
这里引用讨论区解释,具体思路见代码。
将两数组划分为:
double findMedianSortedArraysCut(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size();
int m = nums2.size();
if (n > m) {
return findMedianSortedArraysCut(nums2, nums1);
}
int LMax1, LMax2, RMin1, RMin2, c1, c2, lo = 0, hi = 2 * n;
while (lo <= hi) {
c1 = (lo + hi) / 2; //start:c1=n(/2)
c2 = m + n - c1; //c2=m(/2),nL == nR
LMax1 = (c1 == 0) ? INT_MIN : nums1[(c1 - 1) / 2];
RMin1 = (c1 == 2 * n) ? INT_MAX : nums1[c1 / 2];
LMax2 = (c2 == 0) ? INT_MIN : nums2[(c2 - 1) / 2];
RMin2 = (c2 == 2 * m) ? INT_MAX : nums2[c2 / 2];
if (LMax1 > RMin2)
hi = c1 - 1;
else if (LMax2 > RMin1)
lo = c1 + 1;
else
break;
}
return (max(LMax1, LMax2) + min(RMin1, RMin2)) / 2.0;
}
复杂度分析:首次查找区间为[0,n],其中n=min(n,m),该区间的长度在每次循环之后都会减少为原来的一半。因此,时间复杂度为O(log(min(n,m)));只需要恒定的内存来存储几个局部变量,因此,空间复杂度为O(1)。