开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 15 天,点击查看活动详情
Day43 2023/02/18
难度:简单
题目
示例1
输入:arr1 = {2, 4, 13, 16, 20}, arr2 = {11, 13, 15, 17 ,19}
输出:13
说明:两个升序序列的中位数为13
示例2
输入:arr1 = {2, 4, 13, 16, 19}, arr2 = {20, 20, 20, 20, 20}
输出:19
说明:两个升序序列的中位数为19
思路
- 做本题最简单直接的一种想法大概就是将两个升序序列合并成一个长度为2L的升序序列,中位数就是第L个元素。但是这样的暴力解法时间和空间复杂度较高,所以本题我在暴力解法的基础上做一些优化降低时间和空间复杂度。
- 其实我们没有必要把两个长度为L的升序序列合并,因为我们只需要确定前L个元素的位置即可,后L个元素则不需要考虑,基于此我们可以使用双指针的方式,两个指针分别指向两个数组当前最小值,每次比较指针所指元素的值,更小的一方指针向前移动一步,并且计数器加1,当计数器的值等于L的时候说明我们已经找到了中位数。
具体步骤:
- 循环确定前L个元素的位置,其中每次指针所指元素更小的一方,让其指针向前移动一步,同时计数器加1。
- 当计数器的值等于L的时候就可以返回中位数。
- 如果循环结束仍然没有返回中位数,则返回0,属于未知错误。
关键点
- while循环结束的条件是
dx1 < l && idx2 < l,只要要有一个指针的值大于等于L就跳出循环,因为最坏情况下,其中一个数组所有元素均小于另一个数组的元素(列如示例2),这时候中位数为较小数组的尾元素,所以该数组指针的值要等于L-1,故while的循环条件要这样写。 - 当计数器大于等于L的时候立即返回中位数,因为我们实际上只需要确定前L个元素的位置,所以这样写可以进一步的优化循环的次数。
算法实现
c++代码实现-双指针
#include <iostream>
#include <vector>
using namespace std;
// 从两个升序序列中找出中位数
int FindMedianToTwoArr(vector<int> arr1, vector<int> arr2) {
int median = 0 ,cout = 0; //中位数 ,计数器
int idx1 = 0, idx2 = 0, l = arr1.size(); // 分别为arr1和arr2设置指针,数组长度,两个数组等长
// 确定升序序列中第n位元素
while(idx1 < l && idx2 < l) {
if(arr1[idx1] < arr2[idx2]) {
// 注意这个为什么可以这样写++count,当然推荐你这样分开写count++;if(count == l || count == l + 1)
// 这里我只是为了代码更加简洁!!!
if(++cout == l) median+=arr1[idx1];
idx1++; // 指针加1
}
else{
if(++cout == l) median+=arr2[idx2];
idx2++; // 指针加1
}
if(cout >= l) return median; // 优化循环次数,一旦确定了第n位元素,立即返回
}
return 0; //未知错误,确保健壮性
}
int main() {
//测试以下
//手动定义两个数组
vector<int> arr1 = {2, 4, 13, 16, 20}, arr2 = {11, 13, 15, 17 ,19};
int res = FindMedianToTwoArr(arr1,arr2);
cout << "中位数为:" << res;
return 0;
}
- 时间复杂度 --- 两个指针一起访问了L个元素,其中L为一个数组的长度
- 空间复杂度 --- 只有几个常数级的变量,无额外的辅助空间
总结
- 本题解法仍然还不是时间和空间复杂度最优的解法,但却是相对而言,在空间和时间复杂度较好的情况下,比较好理解的一种解法。
- 本题时间和空间复杂度最优的解法,核心部分是采用二分查找的方式来不断缩小所要查找的中位数的范围,直至找到中位数。其中时间复杂度仅,空间复杂度。