1. 题目来源
2. 题目解析
方法一:递归+边界情况
两个有序数组求中位数。其实,本题还可以拓展为两个有序数组求第 k
小的数。排序、二路归并都稍稍靠边站。采用递归能做到
O
(
l
o
g
(
n
+
m
)
)
O(log(n +m))
O(log(n+m)) 的复杂度。思路如下:
- 首先在两个数组中各取前
k
2
\frac k 2
2k 个元素,记第一个数组为
A
,第二个数组为B
。两个数组中的这两个元素,即有三种大小关系,即:- 当
A[k/2] = B[k/2]
,很好理解,A[k/2]
或B[k/2]
都是可以作为第k
大数的。 - 当
A[k/2] > B[k/2]
,B[k/2]
前面的这k/2
个数字不可能成为第k
小的数。因为B[k/2]
最多只贡献k/2
个,而A[k/2]
却没贡献满k/2
个,故还凑不齐k
个数,第k
小的数只能是B
数组第后半段或者是A
数组中的某个元素。则,可以将B[k/2]
前面这些部分舍去。则问题变成子问题为:从A[1, n],B[k/2 + 1, m]
这两个有序数组中寻找第k - k / 2
小的数。 - 当
A[k/2] < B[k/2]
,同理可得。去掉A[k/2]
这段元素。
- 当
所以,就可以拿递归来做。也很容易发现,每次我们都会去除 k/2
个元素。当在求中位数时,即 k=(m+n)/2
,则整个时间复杂度就是
O
(
l
o
g
(
m
+
n
)
)
O(log(m+n))
O(log(m+n)) 的。
边界问题就是每次在划去 k/2
元素后,是否可能导致越界的问题?这个可以将 A
数组定义为较短数组,B
数组定义为较长数组。由于第一个数组比较短,可能会加 k/2
后导致其越界。故每次更新区间的时候需要和数组元素个数取 min
保证其合法。且由于第二个数组长,则一定不会越界。因为 k=(n+m)/2
则 si
和 sj
不可能同时小于 k/2
。
很难的一道题…
时间复杂度:
O
(
l
o
g
(
m
+
n
)
)
O(log(m+n))
O(log(m+n))
空间复杂度:
O
(
1
)
O(1)
O(1)
代码:
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int tot = nums1.size() + nums2.size();
if (tot % 2 == 0) {
int l = find(nums1, 0, nums2, 0, tot / 2);
int r = find(nums1, 0, nums2, 0, tot / 2 + 1);
return (r + l) / 2.0;
} else {
return find(nums1, 0, nums2, 0, tot / 2 + 1);
}
}
// 从i到nums1最后一个数,从j到nums2最后一个数,找第k小的数
int find(vector<int>& nums1, int i, vector<int>& nums2, int j, int k) {
// 为了方便边界情况判断,假定第一个数组比较短
// 如果第一个数组比较长的话,就先处理第二个数组。交换一下
if (nums1.size() - i > nums2.size() - j) return find(nums2, j, nums1, i, k);
if (k == 1) { // 每次都是k/2,则k最终一定可以等于1。若k==1即仅有一个数了
if (nums1.size() == i) return nums2[j]; // 在此由于nums1比较短,考虑其边界情况。如果i越界,且k为1,则直接输出nums[j]
else return min(nums1[i], nums2[j]);
}
// 如果nums1为空,那么直接返回第二个数组的第k个数,
// 在此k从1开始,当k=1时应当返回nums2[j],所以需要减一
if (nums1.size() == i) return nums2[j + k - 1];
// 递归主体
// si sj 分别为nums1, nums2数组中第k/2个数据的下一个位置,用以更新i, j位置
// 由于第一个数组比较短,可能会加k/2后导致其越界。
// 且由于第二个数组长,则一定不会越界。因为 k=(n+m)/2,则si和sj不可能同时小于k/2
int si = min((int)nums1.size(), i + k / 2), sj = j + k - k / 2;
if (nums1[si - 1] > nums2[sj - 1]) return find(nums1, i, nums2, sj, k - (sj - j)); // 写成k/2也行
else return find(nums1, si, nums2, j, k - (si - i)); // 必须写成 k-(si-i)
}
};
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int tot = nums1.size() + nums2.size();
if (tot % 2 == 0) {
int left = find(nums1, 0, nums2, 0, tot / 2);
int right = find(nums1, 0, nums2, 0, tot / 2 + 1);
return (left + right) / 2.0;
}
else return find(nums1, 0, nums2, 0, tot / 2 + 1);
}
int find(vector<int> &nums1, int i, vector<int> &nums2, int j, int k) {
if (nums1.size() - i > nums2.size() - j) return find(nums2, j, nums1, i, k);
if (k == 1) {
if (nums1.size() == i) return nums2[j];
else return min(nums1[i], nums2[j]);
}
if (nums1.size() == i) return nums2[j + k - 1];
int si = min((int)nums1.size(), i + k / 2), sj = j + k - k / 2;
if (nums1[si - 1] > nums2[sj - 1]) return find(nums1, i, nums2, sj, k - (sj - j));
else return find(nums1, si, nums2, j, k - (si - i));
}
};