一.模板题
给定一个长度为 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
分析:
原理:用单调递减栈,当该元素可以入栈的时候,栈顶元素就是它左侧第一个比它小的元素。
//单调递增栈:栈顶到栈底单调递增
//单调递减栈:栈顶到栈底单调递减
对于待处理的元素,分为三种情况讨论
1.栈顶元素>=待处理元素,则依次将栈顶元素弹出栈,直到栈顶元素小于待处理元素或者栈中无元素为止。
2.栈为空,则直接将待处理元素压入栈
3.栈顶元素<待处理元素,则将待处理元素压入栈,此时栈顶元素即待处理元素左侧第一个比它小的数
来康康例子:
对于3 4 2 7 5:
步骤 | 待处理的元素 | 操作 | 栈(左侧为栈底) | 该元素左侧第一个 比它小的数 |
1 | 3 | 栈为空,3入栈 | [3] | -1 |
2 | 4 | 3<4,4入栈 | [3,4] | 3 |
3 | 2 | 4>2 ,3>2,4和3依次出栈,2入栈 | [2] | -1 |
4 | 7 | 2<7,7入栈 | [2,7] | 2 |
5 | 5 | 7>5,7出栈;2<5,5入栈 | [2,5] | 2 |
代码实现:
#include<bits/stdc++.h>
using namespace std;
stack<int> t;
int main(){
int n,x;
cin>>n;
while(n--){
cin>>x;
//若栈顶元素>=待处理元素,则栈顶元素出栈
while(t.size()&&t.top()>=x)t.pop();
//若栈为空
if(t.size()==0)cout<<"-1 ";
//若栈顶元素<待处理元素,则栈顶元素就是所求值
else cout<<t.top()<<' ';
t.push(x);
}
return 0;
}
或用数组模拟栈
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int stk[N];
int t=-1;
int main(){
int n;
cin>>n;
while(n--){
int x;
cin>>x;
while(t>=0&&stk[t]>=x)t--;
if(t==-1)cout<<"-1 ";
else cout<<stk[t]<<' ';
stk[++t]=x;
}
return 0;
}
二.接雨水
给定 n个非负整数表示每个宽度为 11 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
例如,当给定数字序列为 0,1,0,2,1,0,1,3,2,1,2,1
时,柱子高度图如下所示,最多可以接 6 个单位的雨水。
输入格式
第一行包含整数 n。
第二行包含 n个非负整数。
输出格式
输出一个整数,表示最大接水量。
数据范围
1≤n≤10^5,
序列中元素均不大于 1000。
输入样例:
12
0 1 0 2 1 0 1 3 2 1 2 1
输出样例:
6
分析:
采用单调递增栈(栈顶到栈底单调递增)
设置last,用于标记已计算雨水层数
例如:4 2 1 0 3
- 由于栈为空,所以当前元素的下标直接入栈,栈为 [0],对应值为 [4]
- 由于当前元素2小于栈顶元素h[0],所以当前元素的下标入栈,栈为[0,1],对应值为 [4,2]
- 由于当前元素1小于栈顶元素h[1],所以当前元素的下标入栈,栈为[0,1,2],对应值为 [4,2,1]
- 由于当前元素0小于栈顶元素h[2],所以当前元素的下标入栈,栈为[0,1,2,3],对应值为 [4,2,1,0]
- 由于当前元素3大于栈顶元素h[3],计算储水量res+=(h[3]-last)*(4-3-1),res=0,更新last=h[3];而后弹出栈顶元素,栈为[0,1,2],对应值为[4,2,1]
- 由于当前元素3大于栈顶元素h[2],计算储水量res+=(h[2]-last)*(4-2-1),res=1,更新last=h[2];而后弹出栈顶元素,栈为[0,1],对应值为[4,2]
- 由于当前元素3大于栈顶元素h[1],计算储水量res+=(h[1]-last)*(4-1-1),res=3,更新last=h[1];而后弹出栈顶元素,栈为[0],对应值为[4]
- 由于当前元素3小于栈顶元素h[0],且当前元素下标与栈顶元素下标不相邻,所以形成凹槽,计算储水量res+=(h[i]-last)*(4-0-1),res=6
注:
- 每遍历一个下标,都必须更新last为0
- 使用单调栈,即可找到每个元素左边第一个比它大的值,若该值的下标与其不相邻,说明两个元素之间能够形成凹槽,即能够储存水
- 当前储水量=(两柱子之间的距离)*(栈顶柱子高度-已计算的雨水层高)
- 两柱子之间的距离 l=i-t.top()-1
- 栈顶柱子高度-已计算的雨水层高=h[t.top()]-last
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int n;
int h[N];
stack<int> t;
int main(){
cin>>n;
for(int i=0;i<n;i++)cin>>h[i]; //柱子的高度为h[i]
int res=0; //能接到的雨水总数
for(int i=0;i<n;i++){
int last=0; //储存上一个高度
//判断栈不为空且栈顶元素小于等于当前元素时
while(t.size()!=0&&h[t.top()]<h[i]){
//每次删除一个柱子时,先计算当前柱子与即将删除的柱子间(栈顶元素)的储水量
res+=(h[t.top()]-last)*(i-t.top()-1);
last=h[t.top()];
t.pop();
}
if(t.size()!=0&&i-t.top()>1)res+=(h[i]-last)*(i-t.top()-1);
//当前柱子的下标入栈
t.push(i);
}
cout<<res;
return 0;
}