栈
栈是一种重要的数据结构,它具有后进先出(LIFO)的性质,因此也被简称为LIFO表。
有一种特殊的栈叫做单调栈,它要求维护栈中每个数的大小是单调的。比如写一个单调栈,在压入每一个数时,都要先把栈上面每个大于等于它的数弹出来。这样做的好处是,对于一个序列里的每个数,我们可以很轻松地知道它前面的第一个比它小的数和它后面第一个比它小的数,即它被压入栈时下面的第一个数,和它被弹出时压入栈的数。换句话说,对于维护好的单调栈,取出比任意一个元素大(小)的前后第一个元素的时间复杂度是o(1)
队列
与栈相反,队列具有先进先出(FIFO)的性质,因此也被叫做FIFO表。
通常意义上的队列只能从队尾添加元素、从队首删除元素,两边都能增删元素的队列被称作双端队列。
我们可以用与单调栈同样的思路创造一个单调队列:从队首和队尾都可以出队,只能从队尾入队,要求维护队列的单调性。取出维护好的单调队列里的最大(最小)值的时间复杂度是o(1),因此单调队列在解决滑动窗口等问题时非常好用。
下面我们通过一些题目来理解一下栈和队列。
1.栈和排序
这道题的思路其实很符合直觉,我们需要预处理每个元素到最后一个元素的区间最大值,当把每个元素入栈时,如果这个元素超过了后面所有元素的最大值,就要立刻把它弹出来。
示例代码如下:
查看代码
#include <iostream>
#include <vector>
#include <stack>
using namespace std;int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int n;cin >> n;vector<int> p(n);for (int i = 0; i < n; ++i) {cin >> p[i];}// 预处理后缀最大值,suf_max[i]表示p[i..n-1]中的最大值vector<int> suf_max(n + 1);suf_max[n] = 0; // 哨兵,处理所有元素入栈后剩余最大值为0for (int i = n - 1; i >= 0; --i) {suf_max[i] = max(p[i], suf_max[i + 1]);}stack<int> st;vector<int> ans;for (int i = 0; i < n; ++i) {st.push(p[i]);// 只要栈顶比剩下元素都大,就弹出while (!st.empty() && st.top() >= suf_max[i + 1]) {ans.push_back(st.top());st.pop();}}// 输出结果for (int i = 0; i < ans.size(); ++i) {if (i > 0) cout << ' ';cout << ans[i];}cout << endl;return 0;
}
2.POJ 1028--Web Navigation
根据题干要求,创建一个前向栈和一个后向栈即可。每当执行BACK操作的时候,前向栈入栈一个,后向栈出栈一个,反之同理。如果执行VISIT操作,把前向栈清空。
示例代码如下:
查看代码
#include<bits/stdc++.h>
using namespace std;int main(){stack <string> forw;stack <string> back;string op;while(cin>>op&&op!="QUIT"){if(op=="VISIT"){string url;cin>>url;back.push(url);cout<<url<<endl;while(!forw.empty())forw.pop();}else if(op=="BACK"){if(!back.empty()){forw.push(back.top());back.pop();if(!back.empty())cout<<back.top()<<endl;else{cout<<"http://www.acm.org/"<<endl; } }else cout<<"Ignored"<<endl;}else if(op=="FORWARD"){if(!forw.empty()){cout<<forw.top()<<endl;back.push(forw.top());forw.pop();}else cout<<"Ignored"<<endl;}}
}
3.POJ 2823--Sliding Window
维护一个单调增的队列可以获得窗口内的最小值,维护一个单调减的队列可以获得一个窗口的最大值,注意将过期的元素删除。
下面给出两种实现方法,一种是用数组模拟栈,一种是使用双端队列。
查看代码
#include <cstdlib>
#include <cstring>
#include <iostream>
constexpr int MAXN = 1000100;
using namespace std;
int q[MAXN], a[MAXN];
int n, k;void getmin() { // 得到这个队列里的最小值,直接找到最后的就行了int head = 0, tail = -1;for (int i = 1; i < k; i++) {while (head <= tail && a[q[tail]] >= a[i]) tail--;q[++tail] = i;}for (int i = k; i <= n; i++) {while (head <= tail && a[q[tail]] >= a[i]) tail--;q[++tail] = i;while (q[head] <= i - k) head++;cout << a[q[head]] << ' ';}
}void getmax() { // 和上面同理int head = 0, tail = -1;for (int i = 1; i < k; i++) {while (head <= tail && a[q[tail]] <= a[i]) tail--;q[++tail] = i;}for (int i = k; i <= n; i++) {while (head <= tail && a[q[tail]] <= a[i]) tail--;q[++tail] = i;while (q[head] <= i - k) head++;cout << a[q[head]] << ' ';}
}int main() {cin.tie(nullptr)->sync_with_stdio(false);cin >> n >> k;for (int i = 1; i <= n; i++) cin >> a[i];getmin();cout << '\n';getmax();cout << '\n';return 0;
}
查看代码
#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define MAXN 1000005
#define ll long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define per(i,a,b) for(int i=a;i>=b;i--)
struct node{int pos,val;
}a;
deque<node>maxq,minq;
int maxans[MAXN],minans[MAXN];
int main(){int n,k,x;while(~scanf("%d%d",&n,&k)){while(!maxq.empty())maxq.pop_back();while(!minq.empty())minq.pop_back();rep(i,1,k-1){scanf("%d",&x);while(!maxq.empty()&&maxq.back().val<=x)maxq.pop_back();while(!minq.empty()&&minq.back().val>=x)minq.pop_back();a.pos=i,a.val=x;maxq.push_back(a);minq.push_back(a);}rep(i,k,n){while(!maxq.empty()&&maxq.front().pos<i-k+1)maxq.pop_front();while(!minq.empty()&&minq.front().pos<i-k+1)minq.pop_front();scanf("%d",&x);while(!maxq.empty()&&maxq.back().val<=x)maxq.pop_back();while(!minq.empty()&&minq.back().val>=x)minq.pop_back();a.pos=i,a.val=x;maxq.push_back(a);minq.push_back(a);maxans[i]=maxq.front().val; minans[i]=minq.front().val;}rep(i,k,n){printf("%d%c",minans[i],i==n?'\n':' ');}rep(i,k,n){printf("%d%c",maxans[i],i==n?'\n':' ');}}return 0;
}
