给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的中位数。
算法的时间复杂度应该为 O(log (m+n))
。
(虽然,第一次做的时候,是用归并思路做的,但是归并不符合题意,时间复杂度是O(m+n),而要求是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
首先,题目给的是两个正序的数组nums1
和nums2
,也就是说都是单调递增的序列;其次,示例也给提示了,合并数组后,就很容易判断出中位数是多少。显然,归并做法的时间复杂度是
O(m+n),不符合题意,但我还是放出来了。
class Solution{
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int p1 = 0, p2 = 0;//p1,p2分别指向nums1,nums2
vector<int>& nums(nums1.size() + nums2.size(), 0);//辅助数组
int p = 0;//p指向辅助数组
//归并
while (p1 < nums1.size() && p2 < nums2.size()) {
nums[p++] = nums1[p1] <= nums2[p2] ? nums1[p1++] : nums2[p2++];
}
while (p1 < nums1.size()) nums[p++] = nums1[p1++];
while (p2 < nums2.size()) nums[p++] = nums2[p2++];
//注意:此时p记录的位置是什么?
//无论最后nums1和nums2谁长还是一样长,最后p都会自增一次
//所以,p代表的是nums数组的size
//判读nums奇数长度还是偶数长度
if (p % 2 == 0) return (double)((nums[p / 2] + nums[p / 2 - 1]) / 2);//注意精度
else return (double)nums[p / 2];
}
}
用两个指针分别指向两个数组,谁小谁移动,奇数情况下,直到移动到len/2 + 1
的位置;偶数情况下,移动到len/2, len/2 + 1
。
- 时间复杂度:O(m + n) (同样不符合题意!!!哈哈哈)
代码如下:
class Soluton {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int l1 = nums1.size(), l2 = nums2.size();
//p1-->nums1, p2-->nums2
//pivot1,pivot2 保存可能是中位数的值
int p1 = 0, p2 = 0, pivot1 = 0, pivot2 = 0;
//一共移动 (l1 + l2) / 2 + 1 就刚好到了中位数的位置
//移动规则:谁小就移动谁,知道移动 (l1 + l2) / 2 次
for (int i = 0; i <= (l1 + l2) / 2; ++i) {
//p1相当于慢指针的意思,用来应对偶数情况
pivot1 = pivot2;
pivot2 = (p1 < l1 && (p2 >= l2 || nums[p1] < nums[p2])) ? nums1[p1++] : nums2[p2++];//理解如下
}
return (l1 + l2) & 1 ? pivot2 : (pivot1 + pivot2) / 2.0;
}
}
/*
几个需要注意的地方:
1. pivot2 = (p1 < l1 && (p2 >= l2 || nums[p1] < nums[p2])) ? nums1[p1++] : nums2[p2++];
等价于:
if (p1指针指到了“尾”) { // p1 < l1 不成立
只能移动 p2 指针
} else if (p2指针指到了“尾”) { // p2 >= l2 成立
只能移动 p1 指针
} else { // p1指针和p2指针都没指到“尾”
if (p1指针指向的数 > p2指针指向的数)
移动 p2 指针
else // p1指针指向的数 <= p2指针指向的数
移动 p1 指针
}
2. (l1 + l2) & 1 表示 l1 + l2 是否为奇数,奇数结果为1,否则为0
3. pivot1 = pivot2 这句存在的意义?
每次pivot1都会记录pivot2上轮比较结束之后的结果,只有当 l1 + l2 是奇数的情况下pivot1才有用
*/
时间复杂度为O(log(m+n))的算法,看到log就能想到二分法,所以思路二就是用二分法。
如现在有两个数组:
A:1,3,4,9
B:1,2,3,4,5,6,7,8,9
很明显,它们的中位数是 4
解法:
1. 找中位数,可以看成找两个数组第 K 小的数,这里找第几 K 取决于 A,B 的长度以及他们总长度的奇偶;
2. 首先,寻找两个数组中下标为 K / 2 - 1 的数,比较他们的大小;小的数的左边的数就必定不是第 K 小的数;这时候,就可以排除这几个数,转而去寻找 K - (排除的数) 小的数;然后,重复这个过程;
3. 为什么比较两个数组下标为 K/2 - 1 的数,较小的数组就可以排除呢?
class Solution {
public:
int getKthElement(vector<int>& nums1, vector<int>& nums2, int k) {
int l1 = nums1.size(), l2 = nums2.size();
int index1 = 0, index2 = 0;
while (true) {
int newIndex1 = min(index1 + k / 2 - 1, l1 - 1);
int newIndex2 = min(index2 + k / 2 - 1, l2 - 1);
//这里需要减 1,因为这里是对应到数组的下标
int pivot1 = nums1[newIndex1];
int pivot2 = nums2[newIndex2];
if (pivot1 <= pivot2) {
k -= newIndex1 - index1 + 1;
// newIndex - index + 1 表示要去除的数
index1 = newIndex1 + 1;
// 更新 新的index
}else {
k -= newIndex2 - index2 + 1;
index2 = newIndex2 + 1;
}
}
}
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int totalLength = nums1.size() + nums2.size();
if (totalLength % 2 == 0) { //偶数
return (getKthElement(nums1, nums2, totalLength / 2)
+ getKthElement(nums1, nums2, totalLength / 2 + 1)) / 2.0;
}else { //奇数
return getKthElement(nums1, nums2, (totalLength + 1) / 2);
}
}
}
LeetCode 4.寻找两个正序数组的中位数