双端队列
介绍
双端队列(Deque)是一种具有队列和栈的性质的数据结构。双端队列中的元素可以从两端弹出,相比list增加运算符重载。具体来说,双端队列允许两端都可以进行入队和出队操作。前端进元素排列在队列中后端进的元素前面,后端进的元素排列在队列中前端进的元素的后面。在双端队列入队时,前端进的元素排列在队列中后端进的元素的前面,后端进的元素排列在队列中前端进的元素的后面。在双端队列出队时,无论是前端还是后端出队,先出的元素排列在后出的元素的前面。
此外,双端队列既可以采用顺序存储,也可以采用链式存储实现。双端队列(Deque)既可说是Queue的子接口,也可说Stack(JDK并未提供这个接口)的子接口。因此,Deque即可当成队列使用,也可当成栈使用。
一般将队首进行的操作称之为压入和弹出,队尾进行的操作称之为插入和移除。
可以使用 STL 库中的<deque>实现双端队列。
常见函数
empty()
size()
front()
back()
pop_back()
pop_front()
clear()
push_back()
push_front()
单调队列
单调队列便是用双端队列实现的
单调队列是一种特殊的双端队列,其中的元素遵循单调递增或单调递减的原则。在算法竞赛中,我们一般使用两个单调队列,一个维护单调递增序列,另一个维护单调递减序列。单调队列的队首元素保证是所求区间的最小(最大)值,由于每个元素最多进队和出队一次,因此在求解问题时可以保证时间复杂度为O(1)。
最小值:单调递增
最大值:单调递减
例题
滑动窗口
分别维护一个单调递增和递减的单调队列,队列内存储下标,每次入队后判断最小(最大)值是否在k范围之内,如不在,则出队。
#include<bits/stdc++.h>
using namespace std;
int n,ans,a[1000005],k,sum,minn[1000005],maxx[1000005];
deque<int> q;
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++){
while(!q.empty()&&a[q.back()]>=a[i]){
q.pop_back();
}
q.push_back(i);
if(q.front()<i-k+1){
q.pop_front();
}
minn[i]=a[q.front()];
}
q.clear();
for(int i=1;i<=n;i++){
while(!q.empty()&&a[q.back()]<=a[i]){
q.pop_back();
}
q.push_back(i);
if(q.front()<i-k+1){
q.pop_front();
}
maxx[i]=a[q.front()];
}
for(int i=k;i<=n;i++){
printf("%d ",minn[i]);
}
printf("\n");
for(int i=k;i<=n;i++){
printf("%d ",maxx[i]);
}
return 0;
}
切蛋糕
找到 sum[l-1]的最小值,使sum[r]-sum[l-1]最大
#include<bits/stdc++.h>
using namespace std;
int n,ans,a[500005],k,sum[500005],maxx=-0x3f3f3f;
int minn[500005];
deque<int> q;
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
sum[i]=sum[i-1]+a[i];
}
for(int i=1;i<=n;i++){
while(!q.empty()&&sum[q.back()]>=sum[i]){
q.pop_back();
}
q.push_back(i);
if(q.front()<i-k){
q.pop_front();
}
minn[i]=sum[q.front()];
}
for(int i=1;i<=n;i++){
maxx=max(maxx,sum[i]-minn[i]);
}
printf("%d",maxx);
return 0;
}