难度:困难
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。
示例 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 == mnums2.length == n0 <= m <= 10000 <= n <= 10001 <= m + n <= 2000-106 <= nums1[i], nums2[i] <= 106大致思路就是把两个数组合并到一个新数组里,然后对这个数组进行排序,最后直接求中位数即可,粗暴。虽然通过了力扣测试,但并没有达到时间复杂度的要求。
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int a = nums1.size();
int b = nums2.size();
vector<int> nums(a + b);
int i = 0;
for(; i < a; i++) {
nums[i] = nums1[i];
}
for(; i < a + b; i++) {
nums[i] = nums2[i - a];
}
for(int i = 0; i < a + b; i++) {
for(int j = i; j < a + b; j++) {
if(nums[i] > nums[j]) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
}
if((a + b) % 2) {
return (double)nums[(a + b) / 2];
} else {
return (double)(nums[(a + b) / 2] + nums[(a + b) / 2 - 1]) / 2.0;
}
}
};在题解中,又给我们引入了新的方法:二分查找。
根据中位数的定义,当 m+n 是奇数时,中位数是两个有序数组中的第 (m+n)/22 个元素,当 m+n 是偶数时,中位数是两个有序数组中的第 (m+n)/2 个元素和第 (m+n)/2+1 个元素的平均值。因此,这道题可以转化成寻找两个有序数组中的第 k 小的数,其中 k 为 (m+n)/2 或 (m+n)/2+1。 假设两个有序数组分别是 A 和 B。要找到第 k 个元素,我们可以比较 A[k/2−1] 和 B[k/2−1],其中 / 表示整数除法。由于 A[k/2−1] 和 B[k/2−1] 的前面分别有 A[0 ~ k/2−2] 和 B[0 ~ k/2−2],即 k/2−1 个元素,对于 A[k/2−1] 和 B[k/2−1] 中的较小值,最多只会有 (k/2−1)+(k/2−1)≤k−2 个元素比它小,那么它就不能是第 k 小的数了。 因此我们可以归纳出三种情况:

可以看到,比较 A[k/2−1] 和 B[k/2−1] 之后,可以排除 k/2 个不可能是第 k 小的数,查找范围缩小了一半。同时,我们将在排除后的新数组上继续进行二分查找,并且根据我们排除数的个数,减少 k 的值,这是因为我们排除的数都不大于第 k 小的数。 有以下三种情况需要特殊处理:
用一个例子说明上述算法。假设两个有序数组如下:
A: 1 3 4 9
B: 1 2 3 4 5 6 7 8 9两个有序数组的长度分别是 4 和 9,长度之和是 13,中位数是两个有序数组中的第 7 个元素,因此需要找到第 k=7 个元素。 比较两个有序数组中下标为 k/2−1=2 的数,即 A[2] 和 B[2],如下面所示:
A: 1 3 4 9
↑
B: 1 2 3 4 5 6 7 8 9
↑由于 A[2]>B[2],因此排除 B[0] 到 B[2],即数组 B 的下标偏移(offset)变为 3,同时更新 k 的值:k=k−k/2=4。 下一步寻找,比较两个有序数组中下标为 k/2−1=1 的数,即 A[1] 和 B[4],如下面所示,其中方括号部分表示已经被排除的数。
A: 1 3 4 9
↑
B: [1 2 3] 4 5 6 7 8 9
↑
由于 A[1]<B[4],因此排除 A[0] 到 A[1],即数组 A 的下标偏移变为 2,同时更新 k 的值:k=k−k/2=2。 下一步寻找,比较两个有序数组中下标为 k/2−1=0 的数,即比较 A[2] 和 B[3],如下面所示,其中方括号部分表示已经被排除的数。
A: [1 3] 4 9
↑
B: [1 2 3] 4 5 6 7 8 9
↑
由于 A[2]=B[3],根据之前的规则,排除 A 中的元素,因此排除 A[2],即数组 A 的下标偏移变为 3,同时更新 k 的值: k=k−k/2=1。 由于 k 的值变成 1,因此比较两个有序数组中的未排除下标范围内的第一个数,其中较小的数即为第 k 个数,由于 A[3]>B[3],因此第 k 个数是 B[3]=4。
A: [1 3 4] 9
↑
B: [1 2 3] 4 5 6 7 8 9
↑
作者:LeetCode-Solution
研读了题解之后,我又重新更改了一遍代码:
class Solution {
public:
int getNum(vector<int>& nums1, vector<int>& nums2, int k) {
int a = nums1.size();
int b = nums2.size();
int index1 = 0, index2 = 0;
while(1) {
if(index1 == a) {
return nums2[index2 + k - 1];
}
if(index2 == b) {
return nums1[index1 + k - 1];
}
if(k == 1) {
return min(nums1[index1], nums2[index2]);
}
int newind1 = min(index1 + k / 2 - 1, a - 1);
int newind2 = min(index2 + k / 2 - 1, b - 1);
if(nums1[newind1] <= nums2[newind2]) {
k -= newind1 - index1 + 1;
index1 = newind1 + 1;
} else {
k -= newind2 - index2 + 1;
index2 = newind2 + 1;
}
}
}
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int len = nums1.size() + nums2.size();
if(len % 2) {
return getNum(nums1, nums2, len / 2 + 1);
} else {
return (getNum(nums1, nums2, len / 2) + getNum(nums1, nums2, len / 2 + 1)) / 2.0;
}
}
};虽然代码理论复杂了不少,但不可否认的是,运行快了很多。
用 Java 再次练习了一遍:
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int len = nums1.length + nums2.length;
if(len % 2 == 1) {
return getNum(nums1, nums2, len / 2 + 1);
} else {
return (getNum(nums1, nums2, len / 2) + getNum(nums1, nums2, len / 2 + 1)) / 2.0;
}
}
public int getNum(int[] nums1, int[] nums2, int k) {
int a = nums1.length;
int b = nums2.length;
int index1 = 0, index2 = 0;
while(true) {
if(index1 == a) {
return nums2[index2 - 1 + k];
}
if(index2 == b) {
return nums1[index1 - 1 + k];
}
if(k == 1) {
return Math.min(nums1[index1], nums2[index2]);
}
int newind1 = Math.min(index1 + k / 2 - 1, a - 1);
int newind2 = Math.min(index2 + k / 2 - 1, b - 1);
if(nums1[newind1] <= nums2[newind2]) {
k -= newind1 - index1 + 1;
index1 = newind1 + 1;
} else {
k -= newind2 - index2 + 1;
index2 = newind2 + 1;
}
}
}
}这里挖个坑, 为什么相同代码 Java 的内存消耗和执行用时都比 C++ 要小好多?
在这道题的官方题解中,还提到了第二种方法: 划分数组。
说明 方法一的时间复杂度已经很优秀了,但本题存在时间复杂度更低的一种方法。这里给出推导过程,勇于挑战自己的读者可以进行尝试。 思路与算法 为了使用划分的方法解决这个问题,需要理解「中位数的作用是什么」。在统计中,中位数被用来: 将一个集合划分为两个长度相等的子集,其中一个子集中的元素总是大于另一个子集中的元素。 如果理解了中位数的划分作用,就很接近答案了。







作者:LeetCode-Solution
再次用 C++ 练习了一下这个方法:
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
if(nums1.size() > nums2.size()) {
return findMedianSortedArrays(nums2, nums1);
}
int a = nums1.size();
int b = nums2.size();
int left = 0, right = a;
int medmax = 0, medmin = 0;
while(left <= right) {
int i = (left + right) / 2;
int j = (a + b + 1) / 2 - i;
int med_in1 = i == 0 ? INT_MIN : nums1[i-1];
int med_in2 = j == 0 ? INT_MIN : nums2[j-1];
int med_ax1 = i == a ? INT_MAX : nums1[i];
int med_ax2 = j == b ? INT_MAX : nums2[j];
if(med_in1 <= med_ax2) {
medmax = max(med_in1, med_in2);
medmin = min(med_ax1, med_ax2);
left = i + 1;
} else {
right = i - 1;
}
}
return (a + b) % 2 == 0 ? (medmax + medmin) / 2.0 : medmax;
}
};代码不算很复杂,但推理的过程确实非常巧妙。
今天还是只搞这一道题,一直对时间/空间复杂度的概念不是很清楚,这里记录一下来自维基百科的解释:





在计算机科学中,一个算法或程序的空间复杂度定性地描述该算法或程序运行所需要的存储空间大小。空间复杂度是相应计算问题的输入值的长度的函数,它表示一个算法完全执行所需要的存储空间大小。
和时间复杂度类似,空间复杂度通常也使用大O记号来渐进地表示,例如O(n)、O(nlogn)、

等;其中nnn用来表示输入的长度,该值可以影响算法的空间复杂度。
就像时间复杂度的计算不考虑算法所使用的空间大小一样,空间复杂度也不考虑算法运行需要的时间长短。