480. 滑動視窗中位數
阿新 • • 發佈:2021-02-04
中位數是有序序列最中間的那個數。如果序列的大小是偶數,則沒有最中間的數;此時中位數是最中間的兩個數的平均數。
例如:
[2,3,4],中位數是 3
[2,3],中位數是 (2 + 3) / 2 = 2.5
給你一個數組 nums
,有一個大小為 k
的視窗從最左端滑動到最右端。視窗中有 k
個數,每次視窗向右移動 1
位。你的任務是找出每次視窗移動後得到的新視窗中元素的中位數,並輸出由它們組成的陣列。
示例:
給出 nums = [1,3,-1,-3,5,3,6,7],以及 k = 3。 視窗位置 中位數 --------------- ----- [1 3 -1] -3 5 3 6 7 1 1 [3 -1 -3] 5 3 6 7 -1 1 3 [-1 -3 5] 3 6 7 -1 1 3 -1 [-3 5 3] 6 7 3 1 3 -1 -3 [5 3 6] 7 5 1 3 -1 -3 5 [3 6 7] 6 因此,返回該滑動視窗的中位數陣列 [1,-1,-1,3,5,6]。
提示:
- 你可以假設
k
始終有效,即:k
始終小於輸入的非空陣列的元素個數。
與真實值誤差在10 ^ -5
以內的答案將被視作正確答案。
解答
之前刷過一次了,使用大頂堆維護視窗中較小一半的數,使用小頂堆維護視窗中較大一半的數,並且使大頂堆的大小等於小頂堆大小加一,這樣當視窗大小為奇數時,大頂堆頂就是中位數,如果為偶數時,則取兩個堆頂的平均數。由於堆不支援刪除非堆頂的數,所以這裡採用延遲刪除的技術,即記錄堆中每個數應該出視窗的次數,只要當該數出現在堆頂時,才實際進行刪除。當然具體實現時還有一些細節要注意,如往堆中插入數字時的平滑問題。
class Solution {
public:
vector< double> medianSlidingWindow(vector<int>& nums, int k) {
priority_queue<int> low;
priority_queue<int, vector<int>, greater<int>> high;
unordered_map<int, int> map;
vector<double> result;
for(int i = 0; i < k; i++ ){
low.emplace(nums[i]);
}
for(int i = 0; i < k / 2; i++){
high.emplace(low.top());
low.pop();
}
int i = k;
while(true){
if(k & 1)
result.emplace_back(low.top());
else
result.emplace_back((double(low.top()) + double(high.top())) * 0.5);
if(i >= nums.size())
break;
int num_out = nums[i-k];
int num_in = nums[i];
i += 1;
int need_balance = num_out <= low.top() ? -1 : 1;
map[num_out] += 1;
if(num_in < low.top()){
need_balance += 1;
low.emplace(num_in);
}
else{
need_balance -= 1;
high.emplace(num_in);
}
if(need_balance < 0){
low.emplace(high.top());
high.pop();
need_balance += 1;
}
if(need_balance > 0){
high.emplace(low.top());
low.pop();
need_balance -= 1;
}
while(map[low.top()]){
map[low.top()] -= 1;
low.pop();
}
while(map[high.top()]){
map[high.top()] -= 1;
high.pop();
}
}
return result;
}
};
參考官方題解,對資料結構,以及插入,刪除,平衡等操作做了更好的封裝如下:
class DualHeap {
private:
// 大根堆,維護較小的一半元素
priority_queue<int> small;
// 小根堆,維護較大的一半元素
priority_queue<int, vector<int>, greater<int>> large;
// 雜湊表,記錄「延遲刪除」的元素,key 為元素,value 為需要刪除的次數
unordered_map<int, int> delayed;
int k;
// small 和 large 當前包含的元素個數,需要扣除被「延遲刪除」的元素
int smallSize, largeSize;
public:
DualHeap(int _k): k(_k), smallSize(0), largeSize(0) {}
private:
// 不斷地彈出 heap 的堆頂元素,並且更新雜湊表
template<typename T>
void prune(T& heap) {
while (!heap.empty()) {
int num = heap.top();
if (delayed.count(num)) {
--delayed[num];
if (!delayed[num]) {
delayed.erase(num);
}
heap.pop();
}
else {
break;
}
}
}
// 調整 small 和 large 中的元素個數,使得二者的元素個數滿足要求
void makeBalance() {
if (smallSize > largeSize + 1) {
// small 比 large 元素多 2 個
large.push(small.top());
small.pop();
--smallSize;
++largeSize;
// small 堆頂元素被移除,需要進行 prune
prune(small);
}
else if (smallSize < largeSize) {
// large 比 small 元素多 1 個
small.push(large.top());
large.pop();
++smallSize;
--largeSize;
// large 堆頂元素被移除,需要進行 prune
prune(large);
}
}
public:
void insert(int num) {
if (small.empty() || num <= small.top()) {
small.push(num);
++smallSize;
}
else {
large.push(num);
++largeSize;
}
makeBalance();
}
void erase(int num) {
++delayed[num];
if (num <= small.top()) {
--smallSize;
if (num == small.top()) {
prune(small);
}
}
else {
--largeSize;
if (num == large.top()) {
prune(large);
}
}
makeBalance();
}
double getMedian() {
return k & 1 ? small.top() : ((double)small.top() + large.top()) / 2;
}
};
class Solution {
public:
vector<double> medianSlidingWindow(vector<int>& nums, int k) {
DualHeap dh(k);
for (int i = 0; i < k; ++i) {
dh.insert(nums[i]);
}
vector<double> ans = {dh.getMedian()};
for (int i = k; i < nums.size(); ++i) {
dh.insert(nums[i]);
dh.erase(nums[i - k]);
ans.push_back(dh.getMedian());
}
return ans;
}
};
作者:LeetCode-Solution
連結:https://leetcode-cn.com/problems/sliding-window-median/solution/hua-dong-chuang-kou-zhong-wei-shu-by-lee-7ai6/
來源:力扣(LeetCode)
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。