一、单调栈
主要思想:在找某一数左边大于它或小于它值时,常规算法中的双循环每次都要从头开始遍历,比较了很多没有可能的数,导致算法超时。改用单调栈后,每次都和栈顶元素比较,如果栈顶元素不满足当前条件,即不是现有栈中的最大最小值,也必然不可能是加上下一个数后的新栈中的最大最小值,那么这个元素就没有必要再进行比较,直接出栈,不断重复直到栈顶元素满足最大最小条件后停止。每次遍历都对栈进行此操作就能使栈中元素具有单调性,找最值时直接输出栈顶元素,大大节省了计算时间。
例题:给一个长为n的数组,输出每个数左边第一个小于它的数,如果不存在则输出-1。
#include<iostream>
using namespace std;
const int N=1000;
int stk[N];
int tt;
int main(){
int n;
cin>>n;
for(int i=0;i<n;i++){
int x;
cin>>x;
while(tt&&stk[tt]>=x) tt--;//栈不为空且栈顶元素不满足条件
if(tt) cout<<stk[tt]<<' ';//此时的栈顶元素为第一个满足条件的值
else cout<<-1<<' ';
stk[++tt]=x;
}
return 0;
}
二、单调队列
主要思想:在滑动窗口中找最大最小值,改用思想类似单调栈,为了模拟窗口大小增加队头指针使用单调队列。需要注意窗口大小有限制,要根据窗口大小及时移动队头,在变量数还不够窗口大小时不进行答案输出。
例题:给一个长为n的数组,有一个大小为k的滑动窗口,每次输出窗口中最大的数。
#include<iostream>
using namespace std;
const int N=1000;
int a[N],q[N];
int hh,tt;
int main(){
int n,k;
scanf("%d %d",&n,&k);
for(int i=0;i<n;i++) scanf("%d",&a[i]);
hh=0;//队头、队尾指针初始化
tt=-1;
for(int i=0;i<n;i++){
if(hh<=tt&&i-k+1>q[hh]) hh++;//向右移动滑动窗口
while(hh<=tt&&a[q[tt]]>=a[i]) tt--;//注意单调队列q[]中储存的是下标
q[++tt]=i;
if(i>=k-1) printf("%d ",a[q[hh]]);//填满滑动窗口后再进行答案输出
}
return 0;
}