题目信息
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
示例 1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
示例 3:
输入:nums1 = [0,0], nums2 = [0,0]
输出:0.00000
示例 4:
输入:nums1 = [], nums2 = [1]
输出:1.00000
示例 5:
输入:nums1 = [2], nums2 = []
输出:2.00000
提示:
nums1.length == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= m + n <= 2000
-106 <= nums1[i], nums2[i] <= 106
进阶: 你能设计一个时间复杂度为 O(log (m+n)) 的算法解决此问题吗?
解题思路
法1:暴力
用归并排序的方法在两个数组中找出前(m + n) / 2小的数和下一个小的数,算出中间两个数的平均数即可。
不太熟悉归并排序的同学可以看这里。
优点
好理解,好实现。
缺点
相较于二分查找时间复杂度较高。
时、空复杂度分析
时间复杂度:O(m + n)(将所有归并排序的部分加起来,两个数组各遍历一遍,每个数需要O(1))。
空间复杂度:O(1)。
法2:二分查找
- 只要让j = flag - i,i + j = flag的条件就满足了。
- 然后只要在0 ~ m之间二分查找i的位置就行了。
- 另外,二分查找的停止条件自然是l > r。那么该如何满足这个条件呢?就是当两个左边界的终点都≤两个右边界,即:
① l1 ≤ r1
② l1 ≤ r2※
③ l2 ≤ r1※
④ l2 ≤ r2
数组是有序的,所以①和④不需要考虑。
如果②不满足,说明l1大了,i应该往左走。
我们可以将i的右边界放到i的左边(即i不合法),那么i的左边界也必须往左走了(同时j往右了)。
同理,如果②不满足,j就需要往左走,即i往右走。
优点
时间快,也不难实现。
缺点
不是很好理解。
时、空复杂度分析
时间复杂度:O(log(min(m, n)))(选择两个数组中较短的二分会快一些)。
空间复杂度:O(1)。
代码实现
法1:暴力
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int m = nums1.size(), n = nums2.size(), flag = (m + n) / 2;
int i = 0, j = 0, k = 0; // i、j表示nums1和nums2分别有多少个数的值是它们中前flag个小的
// 归并排序
while (i < m && j < n && k < flag) {
if (nums1[i] <= nums2[j]) {
++i;
} else {
++j;
}
++k;
}
while (k < flag && i < m) {
++i;
++k;
}
while (k < flag && j < n) {
++j;
++k;
}
// 中间两个数(可能只有r)中左边的数
// 从两个左边界中选一个合法并且值最大的
int l = max(i == 0 ? INT_MIN : nums1[i - 1], j == 0 ? INT_MIN : nums2[j - 1]);
// 右边/最中间的数
// 从两个右边界中选一个合法并且值最小的
int r = min(i == m ? INT_MAX : nums1[i], j == n ? INT_MAX : nums2[j]);
// 中间只有一个数就选r,两个数就选l和r的平均数,别忘了转化成小数
return (m + n) % 2 == 1 ? r * 1.0 : (l + r) / 2.0;
}
};
法2:二分查找
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int m = nums1.size(), n = nums2.size(), flag = (m + n) / 2;
// 较短的数组二分更快。
if (m > n) {
return findMedianSortedArrays(nums2, nums1);
}
int i, j; // 右端起点,同时也是左端的长度
int i_l = 0, i_r = m;
int l1, r1, l2, r2;
while (1) {
i = (i_l + i_r) >> 1; // (i_l + i_r) / 2的位运算版
j = flag - i;
// 详见解题思路部分
if (i == 0) {
l1 = INT_MIN;
} else {
l1 = nums1[i - 1];
}
if (i == m) {
r1 = INT_MAX;
} else {
r1 = nums1[i];
}
if (j == 0) {
l2 = INT_MIN;
} else {
l2 = nums2[j - 1];
}
if (j == n) {
r2 = INT_MAX;
} else {
r2 = nums2[j];
}
if (l1 > r2) {
i_r = i - 1;
} else if (l2 > r1) {
i_l = i + 1;
} else { // 四个条件均满足,则可跳出循环。
break;
}
}
// 中间两个数(可能只有r)中左边的数
// 从两个左边界中选一个合法并且值最大的
int l = max(l1, l2);
// 右边/最中间的数
// 从两个右边界中选一个合法并且值最小的
int r = min(r1, r2);
// 中间只有一个数就选r,两个数就选l和r的平均数,别忘了转化成小数
return (m + n) % 2 == 1 ? r * 1.0 : (l + r) / 2.0;
}
};