单调栈
给定一个长度为 N 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1。
输入格式
第一行包含整数 N,表示数列长度。
第二行包含 N 个整数,表示整数数列。
输出格式
共一行,包含 N 个整数,其中第 i 个数表示第 i 个数的左边第一个比它小的数,如果不存在则输出 −1。
数据范围
\(1≤N≤10^5\)
\(1≤数列中元素≤10^9\)
输入样例:
5
3 4 2 7 5
输出样例:
-1 3 -1 2 2
思路
题目需要求每个数左边第一个比它小的数
如果直接暴力的话,时间复杂度是\(o(n^2)\)显然tle了
那么我们可以考虑用空间换时间,将之前扫描过的数存储起来
然后我们需要思考的是之前扫描过的什么数对答案是没有贡献的?
设\(a_{i-2},a_{i-1},a_i\)为连续的三个数,\(a_i\)为当前枚举到的数
显然,如果\(a_{i-2} \ge a_{i-1}\),那么它对答案是没有贡献的(因为如果\(a_{i-1} < a_i\),那么\(a_{i-1}\)就是答案,如果\(a_{i-1} \ge a_i\),那么\(a_{i-2} \ge a_{i-1}\ge a_i\),\(a_{i-2}\)更不可能是答案)
因此,我们只需要维护一个单调递增的栈,每次输出栈顶元素即可
什么是单调栈
栈内元素有单调性,通过维护单调栈实现\(o(n)\)优化
如何维护一个单调栈
单调递增栈:在保持栈内元素单调递增的前提下(如果栈顶元素大于要入栈的元素,将将其弹出),将新元素入栈。
单调递减栈:在保持栈内元素单调递减的前提下(如果栈顶元素小于要入栈的元素,则将其弹出),将新元素入栈。
(图片来源:Thr96)
什么时候使用单调栈
给定一个序列,求序列中的每一个数左边或右边第一个比他大或比他小的数在什么地方;
动图演示
以 3 4 2 7 5 为例,过程如下:
/i/ll/?i=20201211221031165.gif#pic_center
(动图来源:Hasity)
代码1(stl栈)
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
using namespace std;
#define X first
#define Y second
typedef pair<int,int> pii;
typedef long long LL;
const char nl = '\n';
const int N = 1e6+10;
const int M = 2e5+10;
int n,m;
int a[N];
stack<int> s;
void solve(){
cin >> n;
for (int i = 1; i <= n; ++i){
cin >> a[i]; //只用到当前元素
while(s.size() && a[i] <= a[s.top()])s.pop(); //弹出栈内所有大于等于当前元素的数,从而维护单调递增序列
if(s.size())cout << a[s.top()] << " "; //如果栈不空则输出答案
else cout << -1 << " "; //栈空说明答案不存在
s.push(i); //当前元素下标入栈
}
//为什么单调栈储存下标?
//因为如果使用下标储存可以通过下标访问数组,如果使用值储存的话不能通过值访问下标(本题用值储存问题也不大,只是为了方便习惯储存下标)
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
solve();
}
代码2(数组模拟栈)
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
using namespace std;
#define X first
#define Y second
typedef pair<int,int> pii;
typedef long long LL;
const char nl = '\n';
const int N = 1e6+10;
const int M = 2e5+10;
int n,m;
int a[N],st[N],tt;
void solve(){
cin >> n;
for (int i = 1; i <= n; ++i){
cin >> a[i];
while(tt && a[st[tt]] >= a[i])tt --;
if(tt)cout << a[st[tt]] << " ";
else cout << -1 << " ";
st[++ tt] = i;
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
solve();
}
一些问题
1. 为什么单调栈储存下标?
因为如果使用下标储存可以通过下标访问数组,如果使用值储存的话不能通过值访问下标(本题用值储存问题也不大,只是为了方便习惯储存下标)
2. 为什么有STL不用,反而要自己用数组来模拟stack?
- 最致命的原因,99%的测评网站,竞赛系统不会对C++代码使用O2优化,导致使用STL的速度慢,容易造成TLE(超时)。
- 学习栈的操作流程和内部原理。
拓展
- 每个数左边第一个比它大的数
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
using namespace std;
#define X first
#define Y second
typedef pair<int,int> pii;
typedef long long LL;
const char nl = '\n';
const int N = 1e6+10;
const int M = 2e5+10;
int n,m;
int a[N],st[N],tt;
void solve(){
cin >> n;
for (int i = 1; i <= n; ++i){
cin >> a[i];
while(tt && a[st[tt]] <= a[i])tt --;
if(tt)cout << a[st[tt]] << " ";
else cout << -1 << " ";
st[++ tt] = i;
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
solve();
}
- 每个数右边第一个比它小的数
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
using namespace std;
#define X first
#define Y second
typedef pair<int,int> pii;
typedef long long LL;
const char nl = '\n';
const int N = 1e6+10;
const int M = 2e5+10;
int n,m;
int a[N],st[N],tt;
vector<int> ans;
void solve(){
cin >> n;
for(int i = 1; i <= n; i ++ )cin >> a[i];
for (int i = n; i >= 1; i -- ){
while(tt && a[st[tt]] >= a[i])tt --;
if(tt)ans.push_back(a[st[tt]]);//cout << a[st[tt]] << " ";
else ans.push_back(-1);//cout << -1 << " ";
st[++ tt] = i;
}
for (int i = ans.size() - 1; i >= 0; i -- )cout << ans[i] << " ";
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
solve();
}
3.每个数右边第一个比它大的数
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
using namespace std;
#define X first
#define Y second
typedef pair<int,int> pii;
typedef long long LL;
const char nl = '\n';
const int N = 1e6+10;
const int M = 2e5+10;
int n,m;
int a[N],st[N],tt;
vector<int> ans;
void solve(){
cin >> n;
for(int i = 1; i <= n; i ++ )cin >> a[i];
for (int i = n; i >= 1; i -- ){
while(tt && a[st[tt]] <= a[i])tt --;
if(tt)ans.push_back(a[st[tt]]);//cout << a[st[tt]] << " ";
else ans.push_back(-1);//cout << -1 << " ";
st[++ tt] = i;
}
for (int i = ans.size() - 1; i >= 0; i -- )cout << ans[i] << " ";
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
solve();
}