2.栈与队列
1)数组模拟栈
主要思想:先进后出,设置一个数组存储数据,一个栈顶指针指向栈顶(初始化为0)
注意:数据出栈,只是指针单纯前移,无需在意数组还存储数据造成浪费
AcWing 828.模拟栈
#include <iostream>
#include <string>
using namespace std;
const int N=1000010;
int st[N],tt;
int main()
{
int n;
cin>>n;
while(n--)
{
string op;
cin>>op;
if(op=="push")
{
int x;
cin>>x;
st[++tt]=x;
}
else if(op=="pop")
{
tt--;
}
else if(op=="query")
{
cout<<st[tt]<<endl;
}
else
{
if(tt>0) puts("No");
else puts("Yes");
}
}
return 0;
}2)数组模拟队列
主要思想:先进先出,队尾插入,队头删除。设置一个数组存储数据,队头指针指向队列第一个元素(初始为0),队尾指针指向最后一个元素(初始为-1)
AcWing 829.模拟队列
#include <iostream>
#include <string>
using namespace std;
const int N=1000010;
int queue[N],hh,tt=-1;
int main()
{
int n;
cin>>n;
while(n--)
{
string op;
cin>>op;
if(op=="push")
{
int x;
cin>>x;
queue[++tt]=x;
}
else if(op=="pop")
{
hh++;
}
else if(op=="query")
{
cout<<queue[hh]<<endl;
}
else
{
if(hh<=tt) puts("No");
else puts("Yes");
}
}
return 0;
}3)单调栈
主要应用:对一个序列中的某个数,输出这个数左边第一个比他小的数
AcWing 830.单调栈
实现思路:
- 若采用暴力算法,直接两重循环逐个遍历比较,时间复杂度为O(n^2)。
- 采用构造单调栈的思想,降低时间复杂度为O(2n)。每次输入一个数据就判断它左边第一个比他小的数
- 比如对于序列
[3, 4, 2, 7, 5],求解每个数左边最近的且比它小的数(不存在则返回-1)。对于第i个元素,假设j < k < i如果a[k] < a[j], 那么a[j]绝对不会是i的答案(即后来者比前面的小,则答案只会在后来者或者更后来的更小的数中选出,而不会在前面的数中得到,那么这个前面的数就可以除去)。以此构造一个栈,当后来者小于栈顶元素时,栈顶元素不断出栈(弹出栈的元素已失去角逐后续输出答案的机会,后来者居上),然后当前元素入栈(在栈顶),这样就构造了一个单调递增栈。
- 比如对于序列
- 用数组构造一个栈,栈顶指针为tt。每次输出一个数据就将他与当前栈顶元素比较。
- 若栈不为空且栈顶元素大于当前数据,弹出栈,指针tt前移。若直到栈空,则左边不存在比当前元素小的数,输出-1;
- 若找到比当前元素的小的栈顶元素,即为答案输出栈顶元素
- 最后要将当前数入栈,因为当前元素也可能是后来者的答案
#include <iostream>
using namespace std;
const int N=1e6+10;
int stk[N],tt=0,n;
int main(){
scanf("%d",&n);
while(n--){
int x;
scanf("%d",&x);
while(tt && stk[tt]>= x) tt--;
if(tt) printf("%d ",stk[tt]);//当前数左端存在比当前数小的数
else printf("-1");//不存在
stk[++tt]=x;//当前数入栈
}
return 0;
}4)单调队列
主要应用:求滑动窗口中的最小、最大值
AcWing 154. 滑动窗口
实现思路:以输出窗口最小值为例,考虑暴力算法,每滑动一次窗口,都比较k次,寻找到最小值,注意到如果左侧的数据大于右侧的数据,则可以在下一次滑动前删除左侧的数据,以此减少比较的次数,就能构造一个单调递增的队列,每次只需输出队头元素即为窗口内的最小值
- 设置一个滑动窗口队列(队头在左,队尾在右),比较队尾和当前即将进入滑动窗口的数字大小,如果队尾数字大,则删除队尾。因此该队列中的数字全都小于待插入的数字,并且队头是最小的数字,再将该元素插入到队尾,最后输出队头即可。
- 使用一个q[]数组表示该队列,hh指向队头,tt指向队尾前一个位置,注意该数组存储的是对应元素的下标而不是实际元素值。
- 先判断队列队头元素是否还在当前窗口中(用数组下标比较),若不在则队头指针后移,即寻找下一个窗口最小值。
- 循环比较待插入元素和队尾的关系,两种情况
- ①队列不为空且待插元素一直比队尾元素小,则队尾元素不断出队。若直到队列为空,退出当前循环,让待插入元素存入队尾(此时队列仅一个元素)
- ②若出现待插入元素比队尾元素大,那直接退出循环,让待插入元素存到队尾
- 最终就使得队列中的元素是递增的
- 当窗口中的元素已满足k个,满足输出条件,输出最小值,即队头q[hh]所指元素;最大值即队尾元素q[tt]。
其他理解:
AcWing 154. 滑动窗口---海绵宝宝来喽 - AcWing
#include <iostream>
using namespace std;
const int N= 1e6+10;
int a[N],q[N];
int main(){
int n,k;
cin>>n>>k;
for(int i=0;i<n;i++) cin>>a[i];
//输出窗口内最小值
int hh=0,tt=-1;//队头队尾指针初始化
for(int i=0;i<n;i++){
//先判断队头元素是否还在窗口内
while(hh<=tt && i-k+1>q[hh]) hh++; //这里用if也行 因为hh只会加一次
//使队列元素单增
while(hh<=tt && a[q[tt]]>=a[i]) tt--;
q[++tt]=i;//存的是下标
//窗口内已有k个值,可以输出最小值
if(i-k+1>0) cout<<a[q[hh]]<<" ";
}
cout<<endl;
//输出窗口内最大值
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[++tt]=i;//存的是下标
//窗口内已有k个值,可以输出最大值
if(i-k+1>0) cout<<a[q[hh]]<<" ";
}
return 0;
}