先看一道经典题,用两个堆维护中位数
LC 295. 数据流的中位数
题目:元素逐渐增加,求当前数剧的中位数
方法:用一个大根堆维护左边,用一个小根堆维护右边,插入元素时保持两者差不超过1。查找时只需要考虑两个顶点。
其实一种直观的思路是维护有序列表,保持插入和查找都是logn,比如跳表,但是很难写啊
class MedianFinder {
public:
/** initialize your data structure here. */
priority_queue<int>leftQ; // 左边是大根堆
priority_queue<int, vector<int>, greater<int>>rightQ;
MedianFinder() {
}
void addNum(int num) {
if(leftQ.empty() || num <= leftQ.top()) {
leftQ.push(num);
if(leftQ.size() - rightQ.size() > 1) {
int tmp = leftQ.top();leftQ.pop();
rightQ.push(tmp);
}
} else {
rightQ.push(num);
if(rightQ.size() - leftQ.size() > 1) {
int tmp = rightQ.top();rightQ.pop();
leftQ.push(tmp);
}
}
}
double findMedian() {
if(leftQ.size() > rightQ.size()) return leftQ.top();
else if(leftQ.size() == rightQ.size()) return (leftQ.top() + rightQ.top()) *1.0 / 2;
else return rightQ.top();
}
};
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder* obj = new MedianFinder();
* obj->addNum(num);
* double param_2 = obj->findMedian();
*/
一道进阶版的,除了添加还是删除操作
LC 480. 滑动窗口中位数
题目:求长度为k的窗口的中位数
方法一:双堆+延迟删除
插入是一样的,更新堆和个数,删除只做记录和更新个数,不更新堆。不管插入和删除都需要调整平衡。
class Solution {
public:
priority_queue<int>leftQ;
priority_queue<int, vector<int>, greater<int>>rightQ;
unordered_map<int, int>mp;
int leftCnt, rightCnt;
void adjust() {
if(leftCnt - rightCnt > 1) {
int tmp = leftQ.top();leftQ.pop();leftCnt--;
rightQ.push(tmp);rightCnt++;
}
if(rightCnt- leftCnt > 1) {
int tmp = rightQ.top();rightQ.pop();rightCnt--;
leftQ.push(tmp);leftCnt++;
}
}
void addItem(int num) {
if(leftQ.empty() && (!rightQ.empty())) { // k=1的时候,会出现左边为空,而右边有元素的情况
int tmp = rightQ.top();rightQ.pop();rightCnt--;
leftQ.push(tmp);leftCnt++;
}
if(leftQ.empty() || num < leftQ.top()) {
leftQ.push(num);leftCnt++;
} else {
rightQ.push(num);rightCnt++;
}
adjust();
}
void deleteItem(int num) {
mp[num]++; // 记录删除了的
if(num <= leftQ.top()) leftCnt--; // 要用小于等于,因为可能要删除的就是队首
else rightCnt--;
adjust();
}
void pure() {
while((!leftQ.empty()) && mp[leftQ.top()]) {
mp[leftQ.top()]--;
leftQ.pop();
}
while((!rightQ.empty()) && mp[rightQ.top()]) {
mp[rightQ.top()]--;
rightQ.pop();
}
}
double getMedian() {
pure(); // 清理堆头
if(leftCnt > rightCnt) return leftQ.top();
else if(leftCnt < rightCnt) return rightQ.top();
return ((long long)leftQ.top() + (long long)rightQ.top()) * 1.0 / 2;
}
vector<double> medianSlidingWindow(vector<int>& nums, int k) {
int i = 0, j = 0;
while(j < k) {
addItem(nums[j]);
j++;
}
vector<double>ans;
ans.push_back(getMedian());
while(j < nums.size()) {
addItem(nums[j++]);
deleteItem(nums[i++]);
ans.push_back(getMedian());
}
return ans;
}
};
方法二:pbds tree
前面将了,我们可以用跳表做,其实只要能有序列表logn查找第k个的数据结构都行,pbds库就提供了很多tree,都是平衡树。但是有一个问题,他们都不支持重复元素,在这里我们采用pair类型就可以了。
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>
using namespace __gnu_pbds;
// alias template
template <typename T>
using orderd_set = tree<T, null_type, less<T>, rb_tree_tag, tree_order_statistics_node_update>;
class Solution {
public:
tree<pair<int,int>, null_type, less<pair<int, int>>, rb_tree_tag, tree_order_statistics_node_update> order_set;
vector<double> medianSlidingWindow(vector<int>& nums, int k) {
vector<double>ans;
orderd_set<pair<int, int>>myset;
for(int i = 0;i < nums.size();i++) {
if(i < k) myset.insert({nums[i], i});
else {
if(k&1) ans.push_back((*myset.find_by_order(k/2)).first);
else ans.push_back(((long long)(*myset.find_by_order(k/2)).first + (long long)(*myset.find_by_order(k/2-1)).first)*1.0/2);
myset.insert({nums[i], i});
myset.erase({nums[i-k], i-k});
}
}
if(k&1) ans.push_back((*myset.find_by_order(k/2)).first);
else ans.push_back(((long long)(*myset.find_by_order(k/2)).first + (long long)(*myset.find_by_order(k/2-1)).first)*1.0/2);
return ans;
}
};
方法三:multiset+advance
multiset支持重复元素,advance支持查找第K个
class Solution {
public:
vector<double> medianSlidingWindow(vector<int>& nums, int k) {
vector<double>ans;
multiset<double>myset;
for(int i = 0;i < nums.size();i++) {
if(i < k) myset.insert(nums[i]);
else {
auto p = myset.begin();
advance(p, k/2);
if(k&1) ans.push_back(*p);
else ans.push_back(((long long)*p+ (long long)(*prev(p, 1)))*1.0/2);
myset.insert(nums[i]);
myset.erase(myset.find(nums[i-k]));
}
}
auto p = myset.begin();
advance(p, k/2);
if(k&1) ans.push_back(*p); // 最后还有一次
else ans.push_back(((long long)*p+ (long long)(*prev(p, 1)))*1.0/2);
return ans;
}
};