开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第24天,点击查看活动详情
双端队列
双端队列,维护着一个窗口,在这个窗口里保持着单调性。起始有两个指针L和R,都是指向窗口首段。
L和R只能向右增加,不能回退- 双端队列从头部删除过期元素
- 双端队列从尾部增加新的元素
当求窗口内的最大值时,让双端队列从队首到队尾按保持单调递减:双端队列的头始终指向最大值,尾部是最小值。每次取最大值时就直接从队首取出元素即可。此外对于数组,队列里存放的可以是元素下标。
- 队尾:
R指向新的元素。若新元素小于队尾的元素就直接入队;若大于等于队尾的元素,就一直弹出队尾的元素,直到尾部的元素大于新元素或者队列是空。 - 队首:当
L向右移,就检测队首元素是否过期
这里的窗口不是固定的,可以任意扩充缩减。
应用
滑动窗口最大值
对于序列 [1,3,-1,-3,5,3,6,7], k=3。
[1, 3, -1, -3, 5, 3, 6, 7]
0 1 2 3 4 5 6 7
设立一个辅助双端队列 dqeue ,开始时候,L、R 指向队列首。
1) R 右移一位,此时dqeue为空,1从dqeue尾部加入
2)R==3时,3比1大,要保持窗口内元素单调递减,需要将1从尾部弹出,3加入。
3)R==-1,-1比3小,入队。
4) 此时窗口滑过了前三个元素,那么取出三个元素中的最大值,直接从队首取出。
5)R==-3,比-1小,入队。
6) 此时窗口滑过[3,-1,-3],最大值就是此时的队列首元素:3。dd
7)R==5,大于-3,-1,因此需要将元素依次弹出,再将5压入
8)此时窗口滑过[-1,-3, 5],此时最大值就是队列首元素:5。
9)R==3,小于5,入队。
10) 此时窗口滑过[-3, 5, 3],最大值是队列首元素:5
11)R==6,依次将5和3弹出,6入队
10)此时滑动窗口滑过[5,3,6],最大值就是队首:6
11)R==7,弹出6,入队7
12)此时滑动窗口划过[3,6,7],最大值就是队首:7
从队列中删除元素,有两种情况:
- 队首元素过期,即当队首元素和当前元素距离之差超过窗口值k,队首元素就是过期,应该从队首删除
- 新的元素大于等于队尾元素,需要不断地弹出队尾元素,直到满足队尾大于新的元素,或者弹到空
class Solution {
public:
std::vector<int> maxSlidingWindow(std::vector<int>& nums, int k) {
int N = nums.size();
std::vector<int> windMax(N -k+1);
std::list<int> path;
for(int R=0; R < N; ++R) {
while(!path.empty() && nums[path.back()] <= nums[R]) {
path.pop_back();
}
path.push_back(R);
// 新元素的加入可能会导致窗口大于 k
// 使得队列首部元素过期
if(path.front() + k == R) {
path.pop_front();
}
// 只要窗口大于等于k,就需要计算下最大值
if(R >= k-1) {
windMax[R-(k-1)] = nums[path.front()];
}
}
return windMax;
}
};
窗口的最大值减去最小值小于等于 num 的子数组个数
现象:
- 如果一个子数组满足要求,那么这个子数组的任何一个子数组也符合要求:因为这个大的子数组中
max - min <= num,那么这个大的子数组的子数组的肯定也是在[min, max]里面- 如果一个子数组不满足要求,那么这个子数组再怎么扩,也不达标:因为这个子数组
max - min > num,那么扩充只能让max变大min变小,更加不满足。
建立两个双端队列,一个用来保存最大值,一个用来保存最小值。
- 对于数组
arr,L和R开始指向起始位置 R向右扩,扩到第一个不符合要求的元素,那么[L, R)都是满足要求的。 那么以L为开始且符合要求的子数组共有R-L个。- 然后
L右移动,R重复之前动作。
int getNum(std::vector<int> arr, int num) {
if(arr.empty()) return 0;
std::list<int> qmin;
std::list<int> qmax;
int result =0;
// 计算以每个 L 的情况
for(int L =0; L < arr.size(); ++L) {
// R 扩到不能再扩 停止
for(int R=0; R < arr.size(); ++R) {
// 单调递增
while(!qmin.empty() && arr[qmin.back()] >= arr[R]) {
qmin.pop_back();
}
qmin.push_back(R);
// 单调递减
while(!qmax.empty() && arr[qmax.back()] <= arr[R]) {
qmax.pop_back();
}
qmax.push_back(R);
if(arr[qmax.front()] - arr[qmin.front()] > num) {
break;
}
}
// 最小值过期查询
if(qmin.front() == L) qmin.pop_front();
// 最大值过期查询
if(qmax.front() == L) qmax.pop_front();
result += R - L;
}
return result;
}