插入排序法
基本思路
要求数据流中的中位数,每输入一个数,加入到原来的数据流中后,要找到新的数据流的中位数。因此我们可以利用插入排序的方法。每次都在原先有序的数据流中插入新来的数据,形成新的有序数据流。并根据新的数据流的长度,可以在o(1)时间内定位到中位数。
代码
class Solution {
public:
#define SCD static_cast<double>//强制转换
void Insert(int num) {
if(res.empty()) res.push_back(num);
else{//插入排序
vector<int>::iterator pos=lower_bound(res.begin(),res.end(),num);//在原始有序序列中找到第一个大于等于num的位置
res.insert(pos,num);
}
}
double GetMedian() {
int len =res.size();
if(len % 2 == 1) { // 长度为奇数
return SCD(res[len >> 1]);
}else{
return SCD((res[len >> 1]+res[(len - 1) >> 1])) / 2;
}
}
private:
vector<int> res;
};
堆
基本思想
使用堆来解决问题,我们可以构造两个堆,一个大顶堆,放小于中位数的数据;一个小顶堆,放大于中位数的数据。每次来一个新的数,我们要维护堆的两个方面:
- 新来的数,应该放在哪个堆:由于我们希望通过左边的大顶堆和右边的小顶堆来直接确定中位数,所以我们要保证两个堆中元素的数量要一样或者只相差一个元素。因此,我们可以这样设置,
(1)当两个堆的元素的数量一样时,我们把新的数放在右边的小顶堆。
(2)当左边的堆的元素数量比右边的堆的元素数量少时,我们把新的数放在右边的大顶堆中。 - 第二个问题是,我们要维护两个堆。比如第一步中的(1),我们将新的数放在右边小顶堆前,要判断新的数是否比左边的大顶堆的顶要小,
(a) 如果小于左边大顶堆的顶,根据我们之前的约定,左边大顶堆的所有元素要比右边小顶堆的元素小,我们需要将新元素和左边大顶堆的顶交换,即num=max[0],同时,由于左边的大顶堆的顶已经改变了,所以可能不再满足大顶堆的要求,我们需要将左边的重新调整为大顶堆。接着,将更新的num插入到右边的大顶堆中,并重新调整右边的为大顶堆。
(b) 如果大于左边的顶,直接将新的数插到右边,并重新调整右边为小顶堆。
同理,比如第一步中的(2):
(a) 如果大于右边小顶堆的顶,根据我们之前的约定,左边大顶堆的所有元素要比右边小顶堆的元素小,我们需要将新元素和右边小顶堆的顶交换,即num=min[0],同时,由于右边的小顶堆的顶已经改变了,所以可能不再满足小顶堆的要求,我们需要将右边的重新调整为小顶堆。接着,将更新的num插入到左边的大顶堆中,并重新调整左边的为大顶堆。
(b) 如果小于右边的顶,直接将新的数插到左边,并重新调整左边为大顶堆。
代码
class Solution {
public:
#define SCD static_cast<double>
vector<int> min;
vector<int> max;
void Insert(int num){
if(((max.size()+min.size())&1)==0){//偶数
// cout<<"Insert"<<max.size()<<"=="<<endl;
if(max.size()>0&&num<max[0]){// 2.如果t比左边大顶堆的根要小,则把左边大顶堆的根取出来root,将t插入到左边,root插入到右边小顶堆
max.push_back(num);//将元素加入大顶堆
push_heap(max.begin(),max.end(),less<int>());//加入新元素后,重新调整为大顶堆
num=max[0];
pop_heap(max.begin(),max.end(),less<int>()); //弹出堆顶元素
max.pop_back();//将调整后的大顶堆的根弹出来,用于加到小顶堆(右边) //删除最后一个元素
}
// 1.如果 t比左边大顶堆的根要大,则t直接插入到右边的小顶堆 面弹出来的根要加入小顶堆的操作一样
min.push_back(num);
push_heap(min.begin(),min.end(),greater<int>());
}
else{//奇数 ,则可将新输入过来的数t插入到大顶堆:
//这时会有两种情况:1.如果 t比右边小顶堆的根要小,则t直接插入到左边的大顶堆;
//2.如果t比右边小顶堆的根要大,则把右边小顶堆的根取出来root,将t插入到右边,root插入到左边边大顶堆
if(min.size()>0&&num>min[0]){
min.push_back(num);
push_heap(min.begin(),min.end(),greater<int>());
num=min[0];
pop_heap(min.begin(),min.end(),greater<int>());
min.pop_back(); //删除最后一个元素
}
max.push_back(num);
push_heap(max.begin(),max.end(),less<int>());
}
}
double GetMedian(){
if(min.size()>max.size()) { // 长度为奇数
return SCD(min[0]);
}else{
return SCD((max[0]+min[0])) / 2;
}
}
};
优先队列
基本思想
优先队列本身就是一个堆,因此。因此,每来一个新的数,我们只需要判断放在哪个队列就好,优先队列会自动将加入的数据后的数据集,调整为大顶堆或小顶堆。
代码
class Solution {
public:
#define SCD static_cast<double>
priority_queue<int,vector<int>,greater<int> > min;
priority_queue<int> max;
//使用优先队列
void Insert(int num){
if(min.size()==max.size()){
if(max.size()>0&&max.top()>num){//num比大顶堆的顶要小,说明num应该要放在左边,原来左边大顶堆的顶要放在右边
max.push(num);
num=max.top();//更新num
max.pop();//将大顶堆的顶弹出来
}
min.push(num);
}
else{
if(min.size()>0&&min.top()<num){//num比小顶堆的顶要大,说明num应该放在右边,原来有边小顶堆的顶要放在左边
min.push(num);//先入左边的优先队列
num=min.top();//将小顶堆的顶更新到num
min.pop();//将小顶堆的顶弹出
}
max.push(num);//
}
}
double GetMedian(){
if(min.size()>max.size()) { // 长度为奇数
return SCD(min.top());
}
else{
return SCD((min.top()+max.top())) / 2;
}
}
};
总结
刚开始拿到题目,没有理解清,题目的要求。原来数据流是要求一个一个输入,然后再一个一个判断中位数。一开始,我想着的用一个优先队列来保存全部数据,但是我发现,排序后的优先队列,我们不能在O(1)的时间内马上定位到中位数,所以还是需要两个优先队列。编程路上,砥砺前行~