力扣 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;
}
};