1. 程式人生 > >[Leetcode]雙項隊列解決滑動窗口最大值難題

[Leetcode]雙項隊列解決滑動窗口最大值難題

比較 strong xsl 暴力 快捷 c++ 而是 是否 是否為空

這道題是從優先隊列的難題裏面找到的一個題目。可是解法並不是優先隊列,而是雙項隊列deque

其實只要知道思路,這一道題直接寫沒有太大的問題。我們看看題

給定一個數組 nums,有一個大小為 k 的滑動窗口從數組的最左側移動到數組的最右側。你只可以看到在滑動窗口 k 內的數字。滑動窗口每次只向右移動一位。

返回滑動窗口最大值。

示例:

輸入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
輸出: [3,3,5,5,6,7] 
解釋: 

  滑動窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 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       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

翻譯一下就是: 滑動區間裏面每K個最大值

當然暴力naive的解法就顯而易見了,用O(n*k)可以解決,不細說。但還是手癢寫個偽代碼:

for(size_t i=0;i<nums.size();i++){
    int maxValue=INT_MIN;
    for(size_t j=i;j<i+k;j++){
        maxValue=max(maxValue,nums[j]);
    }
    res.push_back(maxValue);
}

我想到的另一種方法是建立一個最大堆。

想法很簡單:每次讀入一個新的數字,就把不在區間範圍內的數字移除堆裏,然後堆的頂部就是此次循環的結果。

一個堆其實非常容易實現。我們甚至可以使用一個C++ STL中的priority_queue。難點就在於,如何把這個不在區間範圍內的數字移除。

However

在這裏我介紹一種更快更通用的方法,使用雙項隊列。這裏為了效率和快捷使用了deque容器,也可以使用list容器。

我聲明了一個變量deque<int>window,用於存儲下標。這個變量有以下特點:

  1. 變量的最前端(也就是window.front())是此次遍歷的最大值的下標
  2. 當我們遇到新的數時,將新的數和雙項隊列的末尾(也就是window.back())比較,如果末尾比新數小,則把末尾扔掉,直到該隊列的末尾比新數大或者隊列為空的時候才停止,做法有點像使用棧進行括號匹配。
  3. 雙項隊列中的所有值都要在窗口範圍內

特點一只是方便我們獲取每次窗口滑動一格之後的最大值,我們可以直接通過window.front()獲得

通過特點二,可以保證隊列裏的元素是從頭到尾降序的,由於隊列裏只有窗口內的數,所以他們其實就是窗口內第一大,第二大,第三大...的數。

特點三就是根據題意設置的。但我們實際上只用比較現在的下標和window.front()就可以了,想想為什麽?

Answer: 因為只要窗口內第一大元素也就是這個window.front()在窗口內,那我們可以不用管第二大第三大元素在不在區間內了。因為答案一定是這個第一大元素。如果window.front()不在窗口內,則將其彈出,第二個大元素變成第一大元素,第三大元素變成第二大元素以此類推。

代碼編寫的過程還要時刻檢查隊列是否為空防止拋出異常。

根據上面這些信息我們就可以編寫此題的代碼了。

Solution

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        if(k==0)return {};
        vector<int>res;
        deque<size_t>window;
        /*Init K integers in the list*/
        for (size_t i = 0; i < k; i++) {
            while (!window.empty()  && nums[i] > nums[window.back()]) {
                window.pop_back();
            }
            window.push_back(i);
        }
        res.push_back(nums[window.front()]);
        /*End of initialization*/
        for (size_t i = k; i < nums.size(); i++) {
            if (!window.empty() && window.front() <= i - k) {
                window.pop_front();
            }
            while (!window.empty() && nums[i] > nums[window.back()]) {
                window.pop_back();
            }
            window.push_back(i);
            res.push_back(nums[window.front()]);
        }
        return res;
    }
};

[Leetcode]雙項隊列解決滑動窗口最大值難題