单调队列指的是队列中的所有元素是单调递增或者单调递减的。
它可以在队首或队尾删除元素,只能在队尾插入元素,维护队列的均摊时间复杂度为O(1)。
单调队列一般用于解决的问题如,用一个长度为k的框在所给序列上移动,求框里面所包含的元素的最大值。
以单调递减队列为例:
队尾插入元素:为了保证队列的递减性,在插入元素num的时候,要将队尾元素和num比较,如果队尾元素不大于num,则删除队尾元素,然后继续将新的队尾元素与num比较,直到队尾元素大于num,这个时候我们才将num插到队尾。
队首删除元素:其实本质上我们只需要保存包括当前元素i在内的前k个元素中的最大值,所以当队首元素的下标小于i - k + 1的时候,就说明当前队首元素的存在已经没有意义了,要将队首元素删除。
核心代码(法一):
int n, k;
int a[100005],queue[100005];
void minQueue() {
int head = 1, tail = 0;
queue[head] = 1;
for (int i = 0; i < n; i++) {
//删除队首元素
if (i - queue[head] == k) head++;
//直接放在队尾
if (head - tail == 1 || a[i] > a[queue[tail]]) {//第一个条件指的是队列为空
queue[++tail] = i;
}
else {//不断删除队尾元素找到合适位置
while (tail >= head && a[i] <= a[queue[tail]]) {
tail--;
}
queue[++tail] = i;
}
if (i >= k - 1) {
printf("%d%c", a[queue[head]], i == n - 1 ? '\n' : ' ');
}
}
}
void maxQueue() {
int head = 1, tail = 0;
queue[head] = 1;
for (int i = 0; i < n; i++) {
//删除队首元素
if (i - queue[head] == k) head++;
//直接放在队尾
if (head - tail == 1 || a[i] < a[queue[tail]]) {//第一个条件指的是队列为空
queue[++tail] = i;
}
else {//不断删除队尾元素找到合适位置
while (tail >= head && a[i] >= a[queue[tail]]) {
tail--;
}
queue[++tail] = i;
}
if (i >= k - 1) {
printf("%d%c", a[queue[head]], i == n - 1 ? '\n' : ' ');
}
}
}
核心代码(法二):
int n, k;
int a[100005];//原序列
int minQueue[100005],maxQueue[100005];//单调递减队列 单调递增队列
int minRe[100005],maxRe[100005];//结果
void solve() {
int minHead = 1, minTail = 0;//单调递减序列头、尾
int maxHead = 1, maxTail = 0;//单调递增序列头、尾
for (int i = 0; i < n; i++) {
//处理单调递减序列
while (minTail >= minHead && a[i] <= a[minQueue[minTail]]) {
minTail--;
}
minQueue[++minTail] = i;
if(i - minQueue[minHead] == k) minHead++;
//处理单调递增序列
while (maxTail >= maxHead && a[i] >= a[maxQueue[maxTail]]) {
maxTail--;
}
maxQueue[++maxTail] = i;
if(i - maxQueue[maxHead] == k) maxHead++;
//存储结果
if(i >= k - 1){
minRe[i] = a[minQueue[minHead]];
maxRe[i] = a[maxQueue[maxHead]];
}
}
//打印结果
for(int i = k - 1;i < n;i++){
printf("%d%c",minRe[i],i == n - 1?'\n':' ');
}
for(int i = k - 1;i < n;i++){
printf("%d%c",maxRe[i],i == n - 1?'\n':' ');
}
}