《队列》专题

队列相关的数据结构和算法。

1. 单调队列

应用:求滑动窗口中的最值。
比如4,5,2,1,-1,3,5
假设窗口大小为3,每次移动一位,输出每次移动后区间中的最小值。
如果用暴力做法,每次移动都要遍历整个窗口寻找,时间复杂度为:移动次数X3

在暴力做法的基础上,我们发现滑动窗口每移动一次,如果新加入的数比它左边的数小,则左边的数在之后的判定中就一定不会是最小值,可以跳过对它的判断来降低时间复杂度。
用双端队列来模拟这个过程,每次新加入的元素n与窗口中其他元素(如a, b, c)逐一比较,比如n比c小,就把c给pop掉,直到n的左侧没有元素比它更大了,就将n给push到队列尾部。

这样就保证我们的窗口队列在每次迭代完后都是单调的,每次移动窗口的最小值就是队列头的值。
我们可以用双端单调队列来完成这件事。要注意的是,因为涉及到删除元素,队列维护的元素必须是原数组的下标,否则难以判断窗口大小是否越界(如此例中>3)。

模拟样例

原数组a 双端队列q 解释
[(4), 5, 1, 1, 3] [0] 单调队列要先填入一个初始0,因为原数组的第一个元素一开始必然是最小值。min=a[q[0]]=4
[(4, 5), 1, 1, 3] [0, 1] 5比4大,4下次还可能是最小值。所以不需pop掉4,而是直接让5进队。min=a[q[0]]=4
[(4, 5, 1), 1, 3] [2] 1小于4和5,只要1在窗口中,答案永远都不可能是4或5,所以队列中4和5的下标都被pop,只剩1的下标2。min=a[q[0]]=1
[4, (5, 1, 1), 3] [3] 又进来一个1,相等时我们把原有的重复元素全部pop,队头元素被更新为3。min=a[q[0]]=1
[4, 5, (1, 1, 3)] [3, 4] 3比1大,把3加入队列。min=a[q[0]]=1

每次操作要检查窗口大小,如果大了就要把队头pop掉。检查的方法很简单:

1
if(q[q.size()-1]-q[0]+1>=k)  pop q.front();  

模板

1
2
3
4
5
6
7
8
9
10
q.push_back(0);
for(int i = 0;i<n;i++)
{
//窗口是否过大
while(q.size()&&k<=q.back()-q.front()+1) q.pop_front();
//使队列保持单调
while(q.size()&&a[q.back()]>=q[i]) q.pop_back();
//新元素入队
q.push_back(i);
}

因为要使用pop_back()所以我们得用双端队列deque。

1.1 滑动窗口的最大值

【题目】

给你一个整数数组 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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sliding-window-maximum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

【ac代码】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
deque<int> q; //注意该双端队列从q.front()进,从q.back()出
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int length = nums.size();
vector<int> re;
for(int i = 0; i<length; i++){

//若下一个待进入窗口元素比队尾元素大,则队尾元素在之后的判定中不可能是最大值,令其出队
//经过本行代码的筛选后,队列中所有元素都比新加入的元素大,且从左至右单调递减
while(!q.empty() && nums[i]>=nums[q.back()]) q.pop_back();

q.push_back(i); //将新元素加入窗口

if(!q.empty() && q.back()-q.front()+1>k) q.pop_front(); //若当前窗口的大小超过k,则队头元素老化被弹出

//题目要求满k个元素才开始求最值因此限制i+1-k>=0
//队列中的元素从左至右单调递减,因此队头元素一定是当前窗口的最大值
if(i+1-k>=0) re.push_back(nums[q.front()]);
}
return re;
}
};

求最小值同理,自己尝试写出。