1.定义
从栈底元素到栈顶元素呈单调递增或单调递减,栈内序列满足单调性的栈;
2.原理
(1)当新元素在单调性上优于栈顶时(单增栈新元素比栈顶大(升序)单减栈新元素比栈顶小 (降序) ),压栈,栈深+1;
(2)当新元素在单调性与栈顶相同(新元素于栈顶相同)或劣于栈顶时(单增栈新元素比栈顶小,单减栈新元素比栈顶大),弹栈,栈深-1;
模拟实现一个在原输入数据的顺序基础上递减的单调栈:
现在有一组数10,3,7,4,12。从左到右依次入栈,则如果栈为空或入栈元素值小于栈顶元素值,则入栈;否则,如果入栈则会破坏栈的单调性,则需要把比入栈元素小的元素全部出栈。单调递减的栈反之。
- 10入栈时,栈为空,直接入栈,栈内元素为10。
- 3入栈时,栈顶元素10比3大,则入栈,栈内元素为10,3。
- 7入栈时,栈顶元素3比7小,则栈顶元素出栈,此时栈顶元素为10,比7大,则7入栈,栈内元素为10,7。
- 4入栈时,栈顶元素7比4大,则入栈,栈内元素为10,7,4。
- 12入栈时,栈顶元素4比12小,4出栈,此时栈顶元素为7,仍比12小,栈顶元素7继续出栈,此时栈顶元素为10,仍比12小,10出栈,此时栈为空,12入栈,栈内元素为12。
3.代码实现
输入
5
10 3 7 4 12
升序排列的单调栈(不重复)
#include <bits/stdc++.h>
using namespace std;
const int N=1e6;
int stk[N],tt=0;
int main()
{
int n,x;
cin>>n;
for (int i = 0; i < n; ++i) {
cin>>x; //升序,要求其左边的数必须小于x,是否取等号?取决于是否重复
while (tt && x<=stk[tt]) tt--; //出栈
stk[++tt]=x;//入栈
}
//输出模拟栈中的元素
for (int i = 1; i <= tt; ++i) cout<<stk[i]<<" ";
return 0;
}
输出:
3 4 12
降序排列
仅第14行的符号变了。
#include <bits/stdc++.h>
using namespace std;
const int N=1e6;
int stk[N],tt=0;
int main()
{
int n,x;
cin>>n;
for (int i = 0; i < n; ++i) {
cin>>x; //降序,要求其左边的数必须大于x
while (tt && x>=stk[tt]) tt--; //出栈
stk[++tt]=x;//入栈
}
//输出模拟栈中的元素
for (int i = 1; i <= tt; ++i) cout<<stk[i]<<" ";
return 0;
}
输出:
12
4.应用
AcWing 830. 单调栈
给定一个长度为N的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出-1。
输入格式
第一行包含整数N,表示数列长度。
第二行包含N个整数,表示整数数列。
输出格式
共一行,包含N个整数,其中第 i 个数表示第 i 个数的左边第一个比它小的数,如果不存在则输出-1。
数据范围
1≤N≤105
1≤数列中元素≤109
输入样例:
5
3 4 2 7 5
输出样例:
-1 3 -1 2 2
思路:
暴力,二重循环:
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N];
int main() {
int n;
cin>>n;
for (int i = 0; i < n; ++i) cin>>a[i];
for (int i = 0,j; i < n; ++i) {
for (j = i-1; j >= 0; --j) {
if (a[j]<a[i]) {
cout<<a[j]<<" "; break;
}
}
if (j==-1) cout<<"-1 ";
}
return 0;
}
我们根据这个过程,观察到一些性质,一些数可能再也不会被用到:
在查找过程中,我们是从第1个数a[0]开始查找的,
第1个数左边肯定不存在数,输出-1,并把a[0]入栈;
第2个数a[1]时,现在开始判断a[1]和a[0]的关系,分两种情况:
- a[1]>a[0] ,此时a[0]符合题目要求:是a[1]左边的第一个比它小的数,可以输出,并把a[1]入栈。
- a[1]<=a[0],此时a[1]左边不满足符合要求的数,那么输出-1,同时由于a[1]<=a[0],所以a[0]就是那个再也不会被用到的数,因为一旦a[0]是符合小于a[i](i>=2),那么a[1]必然符合,且比a[0]靠近a[i],所以不仅要输出-1,还要把a[0]弹出(因为用不到了),再把a[1]入栈。
第3个数a[2],有了第二步的判断,现在只用判断a[2]和a[1]的关系即可:(a[1]一定是左边里面最大的),然后具体的操作情况都和第2步一样。
越往后,栈里面的元素可能有多个,如果a[i]<=a[i-1],a[i-1]被弹出后,不能停止,还要继续判断a[i]与a[i-2]的关系,直到找到满足要求的数或者栈为空为止。即如果栈不为空并且a[i]<=a[i-k],就一直出栈,更适合while语句。
最终,我们得到的是一个在原输入数据的顺序基础上,得到的一个升序的存储数据的栈。
代码实现
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N],tt; //这里采用数组模拟栈来操作,从下标1开始存储
int main()
{
int n,x;
scanf("%d",&n);
for (int i = 0; i < n; ++i) {
scanf("%d",&x);
while(tt && x <= a[tt]) tt--; //找到满足要求的数或者栈为空再停下来
if (tt==0) printf("-1 ");
else printf("%d ",a[tt]);
a[++tt]=x; //新元素无论如何都要入栈
}
return 0;
}