1. 程式人生 > 其它 >力扣 239. 滑動視窗最大值 優先佇列+單調佇列

力扣 239. 滑動視窗最大值 優先佇列+單調佇列

題目

239. 滑動視窗最大值

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

返回 滑動視窗中的最大值 

示例 1:

輸入: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

示例 2:

輸入:nums = [1], k = 1
輸出:[1]

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104
  • 1 <= k <= nums.length

法1.優先佇列

優先佇列介紹

可以檢視這個連線

讓我們通過一個簡單的示例瞭解優先佇列。

在上圖中,我們通過使用push()函式插入了元素,並且插入操作與普通佇列相同。但是,當我們使用pop()函式從佇列中刪除元素時,優先順序最高的元素將首先被刪除。

優先佇列中的元素有優先順序,優先順序高的元素先刪除(大頂堆)

priority_queue <int> q;//預設升序佇列

priority_queue<int,vector<int>,less<int> > q; //另一種構建大頂堆的方法 

priority_queue <int,vector<int>,geater<int> >q;//降序佇列

主要操作

函式 描述
push() 它將新元素插入優先佇列。
pop() 它將優先順序最高的元素從佇列中刪除。
top() 此函式用於定址優先佇列的最頂層元素。
size() 返回優先佇列的大小。
empty() 它驗證佇列是否為空。基於驗證,它返回佇列的狀態。
swap() 它將優先佇列的元素與具有相同型別和大小的另一個佇列交換。
emplace() 它在優先佇列的頂部插入一個新元素。

 題解

 來自官方

主要思想是利用優先佇列的大頂堆,佇列的top一定是值最大的,只需要按順序放入元素就可以依次獲取到最大值。

同時有一個問題,如5,1,1,...,第一個元素5很大,如果不處理那麼後面獲取的最大值會變成5

所以對最大值進行判斷,是否在視窗內,如果不在視窗內就刪除,為了判斷在放入佇列時,只放入元素不夠,可以同時放入元素下標。視窗右端為當前遍歷到的下標i,則左側為i-k

那麼佇列的構造為priority_queue<pair<int,int>> q;,放入的值為q.emplace(nums[i],i);

每次放入元素後就進行檢查最大值下標是否已經出界,在while迴圈中判斷q.top().second(即最大值下標)是否在視窗內,如果出界也只會在視窗左側,

所以判斷seconde<=i-k,i是當前遍歷元素下標,i-k為視窗左端

程式碼
class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> res;
        int len=nums.size();
        //定義優先佇列q
        priority_queue<pair<int,int>> q;
        //先放入前k個元素
        for(int i=0;i<k;i++){
            q.emplace(nums[i],i);
        }
        //獲得前k個元素最大值
        res.push_back(q.top().first);
        //依次滑動視窗
        for(int i=k;i<len;i++){
            q.emplace(nums[i],i);//放入元素
            //判斷最大值是否在視窗外
            while(q.top().second<=i-k){
                q.pop();//移除出界最大值
            }
            res.push_back(q.top().first);//獲得當前視窗最大值
        }
        return res;

    }
};

法2.單調佇列

來自官方

思路是使用一個雙端佇列(首尾都可以插入、刪除元素)q維護一組元素值遞減的下標,同時下標超出視窗的會進行清理。

這樣每次取q.front就可以獲取當前視窗最大值,舉個例子:1,2,1,4,1,1,1,k=3

1.遍歷前K個元素,即初始視窗,判斷當前元素nums[i]和佇列q的尾端元素nums[q.back()],如果當前元素比尾端大,就清理尾端元素(採用while);否則q就加入當前下標i

q:push 1=>pop 1(因為2>1)=>push2=>push 1(因為1<2)   得到[2,1],q.front就是當前視窗最大值

2.滑動視窗,從k開始遍歷,和1.一樣判斷大小進行清理。同時增加下標是否超出視窗範圍的判斷,如果超出就清理掉。

分段講解,先遍歷到i=3時,值為4,視窗[2,1,4]:

q:[2,1]=>pop 1(4>1)=>pop 2(4>2)=>push 4   得到[4],取front為4,即[2,1,4]中最大值4;

當視窗為[4,1,1]時q.front仍是4,可視窗為[1,1,1]時,對4的下標進行判斷,發現出界就進行清理。

這樣維護得到的q在遍歷時取front就可以得到當前視窗最大值。

程式碼

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> res;
        int len=nums.size();
        deque<int> q;//存放未被刪除的下標
        //先放入前k個
        for(int i=0;i<k;i++){
            //如果當前元素比尾端元素大,就pop尾端
            while(!q.empty()&&nums[i]>nums[q.back()]){
                q.pop_back();
            }//然後添加當前元素下標,
            q.push_back(i);
        }
        //第一個視窗的最大值
        res.push_back(nums[q.front()]);
        //滑動視窗
        for(int i=k;i<len;i++){
            //同上
            while(!q.empty()&&nums[i]>nums[q.back()]){
                q.pop_back();
            }
            q.push_back(i);
            //加入元素下標是否出視窗的判斷
            while(q.front()<=i-k){
                q.pop_front();
            }
            //此視窗最大值
            res.push_back(nums[q.front()]);
        }
        return res;
    }
};