1. 程式人生 > 其它 >480. 滑動視窗中位數

480. 滑動視窗中位數

技術標籤:每日一題LeetCode

中位數是有序序列最中間的那個數。如果序列的大小是偶數,則沒有最中間的數;此時中位數是最中間的兩個數的平均數。

例如:

[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)
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。