[Lc]295数据流的中位数
Contents
题目
//Your MedianFinder object will be instantiated and called as such:
MedianFinder* obj = new MedianFinder();
obj->addNum(num);
double param_2 = obj->findMedian();
题解
有三种方法
1. 插入排序法
在插入每一个数时使用插入排序的思路,这样在取数的时候只需要取中间的数(奇数)或者中间两数的平均数(偶数)。
注意插入时使用了upper_bound(),这是函数找到最后一个匹配数的下一个迭代器,或者插入不影响升序。
理论上用upper_bound会更快一些,插入时移动的数字较少。
lower_bound和upper_bound。这两个操作都接受一个关键字,返回一个迭代器。如果关键字在容器中,lower_bound 返回的迭代器将指向第一个具有给定关键字的元素,而upper_bound返回的迭代器则指向最后一个匹配给定关键字的元素之后的位置。如果元素不在multimap 中,则lower_bound 和upper_bound 会返回相等的迭代器一—指向一个不影响排序的关键字插入位置。
——c++ primer 第五版
- 时间复杂度:插入$O(n)+O(logn)$,插入时移动数组与二分查找。
取数$O(1)$ - 空间复杂度$O(n)$,保存数组
class MedianFinder {//三种方法。1.插入排序法
vector<int> nums;//定义数组
public:
/** initialize your data structure here. */
MedianFinder() {}
void addNum(int num) {
if(nums.empty()) nums.push_back(num);//为空直接加入num
else nums.insert(upper_bound(nums.begin(), nums.end(), num),num);//不为空进行插入排序,用upper_bound比lower_bound要移动的数字少
}
double findMedian() {
int n = nums.size();
//分正负取中位数
return n & 1? nums[n/2] : (nums[n/2-1]+nums[n/2])*0.5;//用0.5而不是/2来取小数部分
}
};
2. 两堆法
这个方法利用了堆的性质,在c++中是priority_queue,建立一个大顶堆存小的半部分,一个小顶堆放大的半个部分,注意存数时不能直接存,详见注释
- 时间复杂度:插入$O(logn)$,插入堆消耗,其他操作都是$O(1)$。
取数$O(1)$ - 空间复杂度$O(n)$,保存两个堆
class MedianFinder {//三种方法。2.两堆法
priority_queue<int, vector<int>, less<int> > lo;//大顶堆存小的一半的数字
priority_queue<int, vector<int>, greater<int> > hi;//小顶堆存大的一半的数字
public:
/** initialize your data structure here. */
MedianFinder() {}
void addNum(int num) {
//若两个堆数字数量相等,总数为偶数,此时需要将数字加入hi
//但是不能直接加,因为有可能num属于lo,需要将num加入lo,再把堆顶push给hi
//这里的条件判断起到了平衡两个堆的作用
if(lo.size() == hi.size()){
lo.push(num);
hi.push(lo.top());
lo.pop();
}
//当两个堆数字数量不等,总数为奇数时需要将num加入lo,操作与上面类似,只是相反
else{
hi.push(num);
lo.push(hi.top());
hi.pop();
}
}
double findMedian() {
//用0.5而不是/2,这样才能取float
return lo.size()==hi.size()? (lo.top()+hi.top())*0.5 : hi.top();
}
};
3. 红黑树法(multiset)
也可以直接用自平衡二叉搜索树的性质,multiset的底层实现是红黑树,符合我们的要求(set中不能有重复数组,因此需要用multiset),再定义一个迭代器指针指向中位数(奇数)或中间偏后的数(偶数)即可,每次插入只需要判断奇偶和输入的数属于哪半边,更新迭代器的指向即可,具体更新见注释。根据奇偶输出中位数即可
- 时间复杂度:插入$O(logn)$
- 空间复杂度:$O(n)$
class MedianFinder {//三种方法。3.红黑树法(multiset)
multiset<int> nums;//定义mutliset存数组
multiset<int>::iterator mid;//定义指向中位数的指针
public:
/** initialize your data structure here. */
//这里用委托构造函数初始化mid的值,相比于在构造函数内部初始化效率更高
MedianFinder():mid(nums.end()){}
void addNum(int num) {
const int n = nums.size();//用const int存数组长度
nums.insert(num);//插入新数
//注意n是插入num之前的multiset长度
//只有一个数的时候,mid为这个数
if(!n) mid = nums.begin();
//当num为前半段的数时,判断n的奇偶
//若n为奇数,+1为偶数,mid不变,指向中间偏后的数
//若n为偶数,+1为奇数,指向前一个数,即当前的中位数
else if(num < *mid) mid = n&1? mid : prev(mid);
//当num为后半段的数时,判断n的奇偶
//若n为奇数,+1为偶数,mid指向后一个数,指向中间偏后的数
//若n为偶数,+1为奇数,mid不变,即当前的中位数
else n&1? mid = next(mid) : mid;
}
double findMedian() {
const int n = nums.size();
//取中位数的时候判断奇偶,奇数直接取mid,偶数取mid和前一个数
return n&1? *mid : (*mid+*prev(mid))*0.5;
//也可以这么输出
//return (*mid + *next(mid, n%2-1))*0.5;
}
};
Author ChrisHRZ
LastMod 2020-06-03