食用指南:
对该算法程序编写以及踩坑点很熟悉的同学可以直接跳转到代码模板查看完整代码
只有基础算法的题目会有关于该算法的原理,实现步骤,代码注意点,代码模板,代码误区的讲解
非基础算法的题目侧重题目分析,代码实现,以及必要的代码理解误区
题目描述:
-
给定一个长度为 N 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1。
输入格式
第一行包含整数 N,表示数列长度。
第二行包含 N 个整数,表示整数数列。输出格式
共一行,包含 N 个整数,其中第 i 个数表示第 i 个数的左边第一个比它小的数,如果不存在则输出 −1。数据范围
1≤N≤105
1≤数列中元素≤109
输入样例:
5
3 4 2 7 5
输出样例:
-1 3 -1 2 2 -
题目来源:https://www.acwing.com/problem/content/832/
题目分析:
-
单调栈经典题目
遍历时用栈去维护每个点的靠左最近小于点(小于即可)
将每个节点入栈后,栈内都是一张单调上升的折线图 -
下面来介绍单调栈算法:
算法原理:
模板算法:
- 传送门:静态栈
单调栈:
1. 原理:
-
栈为辅助结构,每个节点入栈时栈都不同,所以说单调栈是一个过程栈
但是共同点都是栈内元素从底到顶可以画作一张单调折线图 -
每个节点入栈时的栈顶就是距离这个节点最近 & 且节点值小于这个节点的节点
所以我们只需要记录每个节点入栈前的栈顶元素即可 -
以arr[5] = {3, 4, 2, 7, 5}填充栈的过程为例
每个过程栈的栈底到栈顶都是单调的 -
遍历arr[i]时候的折线图:
当栈顶元素大于等于将进栈元素 反映在折线图上就是新加点和原本最靠右点的连线是下降的,此时尝试与原本点建立上升连线,丢弃下降连线的点
2. 步骤:
-
构建一个栈,遍历序列时,将序列元素arr[i]和栈顶元素比较大小
-
若arr[i]小于栈顶元素
-
一直出栈,直到栈顶元素小于arr[i],为找到arr[i]的最近小值
-
之后将arr[i]放入栈中,成为新的栈顶元素,
刚才出栈的元素必然不如a小且不如a近于之后元素,
这样便于接下来的元素查找其最近小值
-
-
若a大于栈顶元素,则栈顶元素即a的最近小于值,arr[i]直接入栈
3. 核心:
- 核心就两点:
- 出栈顶:
一方面确保栈底到栈顶的单调性,另一方面找到该点的最近小于节点 - 入栈:
当前元素也加入栈中,这是为后续元素铺垫- 后续元素大于该元素,该元素就是后续元素的最近小于节点
- 后续元素小于等于该元素,直接出栈即可,无不良影响
- 总结一下就是:加进去试试,能行最好,不能行也不影响
- 出栈顶:
代码实现:
const int N = 100010;
int stk[N], tt; //静态栈和栈顶即可,不需要stack<>
int main(){
int n = 0;
cin >>n;
for(int i=0; i<n; i++){
int x=0;
cin >>x;
//注意点1:出栈顶
while(tt && stk[tt] >= x) tt--;
if(tt) cout<<stk[tt] <<endl;
else cout<<-1 <<endl;
//注意点2:入栈顶
stk[++tt] = x;
}
}
代码误区:
1. 出栈顶:
- 利用while()的if()功能,不断进行当前节点和栈顶节点的比较
2. 输出问题:
- 栈顶若未出完,则栈顶即使当前的最近小于节点
- 栈顶元素出完了,则当前元素无最近小于节点,当前元素为最小,后续元素也直接从当前元素进行考量
3. 对过程栈的理解:
- 每个节点的单调栈都是通过比较确定的,每个单调栈也是不同的
- 想要查询某个节点的最近小于节点,只能去查当时的单调栈栈顶,不能在这个节点过后或提前查询
- 入栈前的栈顶是最近小于节点
本篇感想:
- 单调栈就三句话:
- 先出栈顶看看
- 出完后的栈顶是该点答案
- 该点入栈顶
- 难点其实是对单调栈是过程栈的理解
- 看完本篇博客,恭喜已登 《练气境-后期》
35篇时将进入图论,对dfs bfs不熟悉的同学看这里:用C++类和队列实现图搜索的广度优先遍历算法
距离登仙境不远了,加油