4.寻找两个正序数组的中位数
一、问题描述
给定两个大小分别为
m
和n
的正序(从小到大)数组nums1
和nums2
。请你找出并返回这两个正序数组的 中位数 。
二、问题简化
所谓中位数,就是指一个数组中间位置的数字。当元素数量为奇数时,就是最中间的那个数字;而当元素数量为偶数时,为中间两个数字的平均值(求和除以2)。
这里用数列表示并不恰当,数列的定义是正整数集。所以仍旧以模板T抽象表示元素类型,不过对于抽象的类型T来说,我们要求中位数就必须使用求和操作和除以2的操作,再考虑到整型除以2会发生截断操作,干脆就只返回数字即可:
//返回值类型,0个元素代表原数组均为空,1个元素代表奇数,2个元素代表偶数
vector<T>
这里的问题实际并没有描述清楚,中位数本质是在一个数组中寻找,而题目的意思是合并数组后再寻找。所以也不一定是只合并两个数组,所以传入数组的参数如下:
list<vector<T>*>
所以函数接口为:
template<typename T>
vector<T> findMedian(list<vector<T>*>);
三、功能实现
最容易想到的办法是按序合并所有数组后,再判断奇偶返回对应的值。
而合并数组不仅会占据所有数组大小的空间,并且还涉及到元素的拷贝,这样就太慢了!这个区别不是数量级的区别,而是做一千个俯卧撑和动一千下手指的区别。
1.求出合并后的数组总长度M(整型),很明显我们并不需要真的合并,总长度就是各个数组长度之和。
2.如果M为奇数,则中位数为M/2位置的数;如果M为偶数,则中位数为M/2 - 1和M/2位置的数的平均值。(整型除法,直接截断)
3.如何遍历到指定位置的数?每个数组记录下一个值,为当前位置。每次遍历从数组当前位置选择最小值的那个,然后选中的那个数组当前位置增加1。(这实际是一个可抽象的问题,从N个有序数组中,返回合并后的数组,指定位置的元素)
所以我先实现了从N个有序数组中寻找指定位置的元素这个功能:
//从N个有序数组中寻找 指定位置的元素
//所有参数均为升序
template<typename T>
vector<T> FindSortedArrays(const vector<vector<T>*>& all_vec, const vector<int>& vec_target, int m = -1)
{
//没有数组,返回错误
if (all_vec.empty()
|| vec_target.empty())
return vector<T>();
//总长度
if (m == -1)
{
m = 0;
for (auto& iter : all_vec)
{
m += iter->size();
}
}
//总长度错误
if (m <= 0)
return vector<T>();
int all_vec_size = all_vec.size();
//每个数组设置一个位置标记
vector<int> arr_pos(all_vec_size, 0);
//目标位置 当前
int i_target = 0;
vector<T> ret;
for (int i = 0; i != m; ++i)
{
//从所有数组的当前位置返回最小值
bool b_inited_min = false;
T v_min;
int j_min;//最小值所在的j
for (int j = 0; j != all_vec_size; ++j)
{
vector<T>& vec_ref = *all_vec[j];
int cur_pos = arr_pos[j];
//越界跳过
if (cur_pos >= vec_ref.size())
continue;
//最小值未初始化
if (!b_inited_min)
{
b_inited_min = true;
v_min = vec_ref[cur_pos];
j_min = j;
}
else
{//已有最小值,则比较覆盖
if (vec_ref[cur_pos] < v_min)
{
v_min = vec_ref[cur_pos];
j_min = j;
}
}
}
//断言:未找到最小值
if (!b_inited_min)
{
return vector<T>();
}
//最小数组当前位置加1
++arr_pos[j_min];
if (i == vec_target[i_target])
{
ret.push_back(v_min);
if (++i_target >= vec_target.size())
return ret;
}
}
return ret;
}
测试结果如下:
我一共输入了4个数组,并且通过FindSortedArrays函数直接返回了合并之后的指定位置值(函数内部没有实际的合并操作,这个函数叫GetSortedArrays应该更合适)。
然后寻找中位数就变得很简单,通过&1判断奇偶即可,奇数返回m/2位置的值,偶数返回m/2 - 1 ,m/2的平均值:
//寻找中位数
template<typename T>
vector<T> FindMedian(const vector<vector<T>*>& all_vec)
{
//没有数组,返回错误
if (all_vec.empty())
return vector<T>();
//计算总长度
int m = 0;
for (auto& iter : all_vec)
{
m += iter->size();
}
//奇数
if (m & 1)
{
return FindSortedArrays(all_vec, { m / 2 }, m);
}
else
{
return FindSortedArrays(all_vec, { m / 2 - 1, m / 2 }, m);
}
}
四、测试结果
我再复制几组数据用于测试:
网上代码的一般思路是实际合并了数组再直接返回对应位置的值,而这个合并操作就占据了大量的空间和时间,而我的代码的优势在于不必占据大量的临时空间,还可以减少合并时的拷贝操作。但是仍然需要比较很多次和拷贝一定次数。
等有空了我再补充测试一下它们之间到底谁更快。不过我在LeetCode里面有测试,经过我反复尝试很明显编译器没有做优化,而且输入数据量也很低,它的速度没什么参考价值。
完整代码如下:
#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
using namespace std;
//从N个有序数组中寻找 指定位置的元素
//所有参数均为升序
template<typename T>
vector<T> FindSortedArrays(const vector<vector<T>*>& all_vec, const vector<int>& vec_target, int m = -1)
{
//没有数组,返回错误
if (all_vec.empty()
|| vec_target.empty())
return vector<T>();
//总长度
if (m == -1)
{
m = 0;
for (auto& iter : all_vec)
{
m += iter->size();
}
}
//总长度错误
if (m <= 0)
return vector<T>();
int all_vec_size = all_vec.size();
//每个数组设置一个位置标记
vector<int> arr_pos(all_vec_size, 0);
//目标位置 当前
int i_target = 0;
vector<T> ret;
for (int i = 0; i != m; ++i)
{
//从所有数组的当前位置返回最小值
bool b_inited_min = false;
T v_min;
int j_min;//最小值所在的j
for (int j = 0; j != all_vec_size; ++j)
{
vector<T>& vec_ref = *all_vec[j];
int cur_pos = arr_pos[j];
//越界跳过
if (cur_pos >= vec_ref.size())
continue;
//最小值未初始化
if (!b_inited_min)
{
b_inited_min = true;
v_min = vec_ref[cur_pos];
j_min = j;
}
else
{//已有最小值,则比较覆盖
if (vec_ref[cur_pos] < v_min)
{
v_min = vec_ref[cur_pos];
j_min = j;
}
}
}
//断言:未找到最小值
if (!b_inited_min)
{
return vector<T>();
}
//最小数组当前位置加1
++arr_pos[j_min];
if (i == vec_target[i_target])
{
ret.push_back(v_min);
if (++i_target >= vec_target.size())
return ret;
}
}
return ret;
}
//寻找中位数
template<typename T>
vector<T> FindMedian(const vector<vector<T>*>& all_vec)
{
//没有数组,返回错误
if (all_vec.empty())
return vector<T>();
//计算总长度
int m = 0;
for (auto& iter : all_vec)
{
m += iter->size();
}
//奇数
if (m & 1)
{
return FindSortedArrays(all_vec, { m / 2 }, m);
}
else
{
return FindSortedArrays(all_vec, { m / 2 - 1, m / 2 }, m);
}
}
int main()
{
{
cout << "-----------------------------------All arrays------------------------------" << endl;
vector<int> vecs[] = {
{1,2,5,11,14,99,105},
{3,6,8,9,12,13},
{2,107,113,120,999},
{-1, 117}
};
//合并并排序
vector<int> vecs_add;
for (auto& iter : vecs)
{
//打印
for (auto& iter1 : iter)
{
cout << iter1 << " ";
}
cout << endl;
vecs_add.insert(vecs_add.end(), iter.begin(), iter.end());
}
sort(vecs_add.begin(), vecs_add.end());
//打印合并之后
cout << "-----------------------------------After the merger is------------------------------" << endl;
for (auto& iter1 : vecs_add)
{
cout << iter1 << " ";
}
cout << endl;
//
vector<vector<int>*> vec_temp;
for (auto& iter : vecs)
vec_temp.push_back(&iter);
vector<int> vec_find = { 0,5,10,12 };//要寻找的位置
auto ret0 = FindSortedArrays(vec_temp, vec_find);
//打印查找结果
cout << "-----------------------------------Find results------------------------------" << endl;
for (int i = 0; i != ret0.size(); ++i)
{
cout << "index " << vec_find[i] << " is " << ret0[i] << endl;
}
//
auto ret1 = FindMedian(vec_temp);
//打印中位数
cout << "-----------------------------------Median is------------------------------" << endl;
if (ret1.size() == 1)
{
cout << ret1[0] << endl;
}
else if (ret1.size() == 2)
{
//转为小数
cout << float(ret1[0] + ret1[1]) * 0.5f << endl;
}
else
{
cout << "error" << endl;
}
}
cout << "================================================================================" << endl;
{
cout << "-----------------------------------All arrays------------------------------" << endl;
vector<int> vecs[] = {
{5,6,7}
};
//合并并排序
vector<int> vecs_add;
for (auto& iter : vecs)
{
//打印
for (auto& iter1 : iter)
{
cout << iter1 << " ";
}
cout << endl;
vecs_add.insert(vecs_add.end(), iter.begin(), iter.end());
}
sort(vecs_add.begin(), vecs_add.end());
//打印合并之后
cout << "-----------------------------------After the merger is------------------------------" << endl;
for (auto& iter1 : vecs_add)
{
cout << iter1 << " ";
}
cout << endl;
//
vector<vector<int>*> vec_temp;
for (auto& iter : vecs)
vec_temp.push_back(&iter);
vector<int> vec_find = { 1, 3 };//要寻找的位置
auto ret0 = FindSortedArrays(vec_temp, vec_find);
//打印查找结果
cout << "-----------------------------------Find results------------------------------" << endl;
for (int i = 0; i != ret0.size(); ++i)
{
cout << "index " << vec_find[i] << " is " << ret0[i] << endl;
}
//
auto ret1 = FindMedian(vec_temp);
//打印中位数
cout << "-----------------------------------Median is------------------------------" << endl;
if (ret1.size() == 1)
{
cout << ret1[0] << endl;
}
else if (ret1.size() == 2)
{
//转为小数
cout << float(ret1[0] + ret1[1]) * 0.5f << endl;
}
else
{
cout << "error" << endl;
}
}
cout << "================================================================================" << endl;
{
cout << "-----------------------------------All arrays------------------------------" << endl;
vector<int> vecs[] = {
{5,6,7},
{1,1,1,1,2,2,2,5,6,7},
{9,11,100}
};
//合并并排序
vector<int> vecs_add;
for (auto& iter : vecs)
{
//打印
for (auto& iter1 : iter)
{
cout << iter1 << " ";
}
cout << endl;
vecs_add.insert(vecs_add.end(), iter.begin(), iter.end());
}
sort(vecs_add.begin(), vecs_add.end());
//打印合并之后
cout << "-----------------------------------After the merger is------------------------------" << endl;
for (auto& iter1 : vecs_add)
{
cout << iter1 << " ";
}
cout << endl;
//
vector<vector<int>*> vec_temp;
for (auto& iter : vecs)
vec_temp.push_back(&iter);
vector<int> vec_find = { 1, 3 };//要寻找的位置
auto ret0 = FindSortedArrays(vec_temp, vec_find);
//打印查找结果
cout << "-----------------------------------Find results------------------------------" << endl;
for (int i = 0; i != ret0.size(); ++i)
{
cout << "index " << vec_find[i] << " is " << ret0[i] << endl;
}
//
auto ret1 = FindMedian(vec_temp);
//打印中位数
cout << "-----------------------------------Median is------------------------------" << endl;
if (ret1.size() == 1)
{
cout << ret1[0] << endl;
}
else if (ret1.size() == 2)
{
//转为小数
cout << float(ret1[0] + ret1[1]) * 0.5f << endl;
}
else
{
cout << "error" << endl;
}
}
}