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