1. 程式人生 > 其它 >程式碼手記筆錄——優先佇列與單調佇列

程式碼手記筆錄——優先佇列與單調佇列

優先佇列

參考:https://zhuanlan.zhihu.com/p/478887055

優先佇列採用堆結構儲存資料,高優先順序大頂堆。 可以重寫比較器 cmp 設定優先級別。對於基本資料型別,預設為數值大的優先順序高:

// 二者等價,數值大的優先順序高
priority_queue<int>  prq;   // (1)
priority_queue<int, vector<int>, less<int>>  prq;   // (2)

// 二者等價,設定數值小的優先順序高
priority_queue<int, vector<int>, less<int>>  prq;   // (1)
priority_queue<int, vector<int>, cmp>  prq;   // (2)
bool cmp(int &a, int &b) {
  return a > b;
}

對於類物件型別的 cmp 重寫方式如下:

class fruit {
	string name;
	int price;
	friend operator <(fruit &f1, fruit &f2) {
		return f1.price < f2.price; // price 數值高優先順序大
		// return f1.price > f2.price; // price 數值小優先順序大
	}
};

優先佇列適合求區域性最值問題。

經典例題

239 滑動視窗最大值


(1)將視窗內元素放入優先佇列,並每次取隊頭元素即可得到最大值;
(2)刪除不在視窗的隊頭元素,即可保證每次隊頭元素為區域性最大值。

  • 時間複雜度分析:每個元素只入隊、出隊一次,故為 O(n)。
  • 空間複雜度分析: O(n)
class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        priority_queue<pair<int, int>> prq;
        vector<int> vec;
        int n = nums.size(), topN, topIdx;
        for (int i=0; i<k; ++i)
            prq.push(make_pair(nums[i], i));
        vec.push_back(prq.top().first);  // 滑動視窗移動啟動時的最大值
        for (int i=k; i<n; ++i) {
            prq.push(make_pair(nums[i], i));
            // 若當前優先佇列的最大值不在視窗內,就讓其 pop 掉
            while (prq.top().second <= i-k)
                prq.pop();
            vec.push_back(prq.top().first);
        }
        return vec;
    }
};

單調佇列

參照:https://blog.51cto.com/u_13281972/2997983
一個序列只有頭尾的資料有變動,需要求該序列的最大值或最小值,可以嘗試使用單調佇列。

經典例題

239 滑動視窗最大值


演算法思想:單調佇列裡存放陣列下標,且保證下標從小到大排序,同時保證下標對應的元素也從小到大排序。佇列裡值大者放前,每指向一元素,就從隊尾入手將佇列裡小於該元素值的資料彈出【隊尾彈出機制保證了下標對應的元素也從小到大排序】。滑動視窗從左向右移動【移動方向保證了下標從小到大排序】。選取最大值時需彈出隊頭以確保落在滑動視窗內【隊頭彈出機制保證了所求最大值落在滑動視窗內】。

  • 時間複雜度分析:每個元素只入隊、出隊一次,故為 O(n)。
  • 空間複雜度分析:單調佇列元素最多不超過滑動視窗個數,為 O(k)
class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        deque<int> dq;
        vector<int> vec;
        for (int i=0; i<k; ++i) {
            while (!dq.empty() && nums[dq.back()] <= nums[i])
                dq.pop_back();
            dq.push_back(i);
        }
        vec.push_back(nums[dq.front()]);
        for (int i=k; i<nums.size(); ++i) {
            // 單調佇列每插入一個元素,都將其前面小於它的元素彈出
            while (!dq.empty() && nums[dq.back()] <= nums[i])
                dq.pop_back();
            dq.push_back(i);
            // 由於視窗在移動,因此每移動一次,就將不在視窗內的元素彈出
            while (dq.front() <= i-k)
                dq.pop_front();
            vec.push_back(nums[dq.front()]);
        } 
        return vec;
    }
};