算法总结——单调栈

在这里插入图片描述
纵有疾风起,人生不言弃。本文篇幅较长,如有错误请不吝赐教,感谢支持。

文章目录

    • 一、单调栈的定义
    • 二、单调栈的应用:
      • 寻找左边第一个比它小的数
      • 单调栈的思想(重点):
      • 寻找左边第一个比它小的数的下标
      • 寻找右边第一个小于它的数
      • 寻找右边第一个小于它的数的下标
      • 单调栈总结

一、单调栈的定义

单调栈不是一种新的数据结构,它在结构上仍然是一个普通栈,只是在使用方法上有所区别。单调栈内的元素是单调递增或递减的,因此有单调递增栈和单调递减栈。

  • 单调递增栈: 栈中元素从栈底到栈顶是递增的。
  • 单调递减栈: 栈中元素从栈底到栈顶是递减的。

我们在使用单调栈的时候,要时刻保证栈的单调性,例如,单调递增栈:栈中元素从栈底到栈顶是递增的。当一个元素入栈前从栈顶依次去除所有大于等于该入栈元素的元素,直到栈顶小于该入栈元素,此时元素可以入栈,栈的单调性没被破坏。单调递减栈相反。注意:每个元素都一定入栈
单调栈的核心思想就是:

及时去掉无用数据,保证栈中数据有序

二、单调栈的应用:

单调栈的应用:用来在一个数组中寻找某一个元素左边(或右边)第一个大于(或小于或大于等于或小于等于)它的元素(或元素的下标)。

寻找左边第一个比它小的数

题目练习: AcWing 830. 单调栈
给定一个长度为 N 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1。
输入格式
第一行包含整数 N,表示数列长度。
第二行包含 N 个整数,表示整数数列。
输出格式
共一行,包含 N 个整数,其中第 i 个数表示第 i 个数的左边第一个比它小的数,如果不存在则输出 −1。
数据范围
1≤N≤10 5 {5} 5
1≤数列中元素≤ 1 0 9 10^{9} 109
输入样例:

5
3 4 2 7 5

输出样例:

-1 3 -1 2 2

思路:传统的暴力做法是双重循环,两个指针i和j,i用来遍历序列,j用来扫描i左边的所有数。时间复杂度是O( n 2 n^{2} n2)

#include<iostream>using namespace std;const int Max = 1e5 + 10;int n,arr[Max];int main() {cin >> n;for (int i = 0; i < n; i++) cin >> arr[i];for (int i = 0; i < n; i++) {bool flag = false;//标志 for (int j = i - 1; j>=0; j--) {if (arr[j] < arr[i]) {flag = true;cout << arr[j] << ' ';break;}}if (!flag) cout << -1 << ' ';//如果flag为false,说明左边没有比该元素小的 } return 0;
}

我们要是使用单调栈可以将时间复杂度优化到O(n)。

单调栈的思想(重点):

及时去掉无用数据,保证栈内数据有序

在指针 i 从左往右遍历的过程中,我们可以用一个单调栈来保存 i左边的所有元素(不包括i指向的元素),i遍历一个元素单调栈就放入一个元素,每一个元素都必须入栈一次,下标越大的元素越接近栈顶,下标越小的元素越接近栈底
我们想要让元素入栈,就要保证单调栈的单调性不变。

那么如何保证放入元素时栈的单调性不变呢?
单调递增为例:在元素入栈前从栈顶依次去除所有无用数据(大于等于该入栈元素的元素),因为这些元素会破坏单调性,直到栈顶小于该入栈元素,此时元素可以入栈,栈的单调性没被破坏,并且此时的栈顶就是要寻找的答案——左边第一个比它小的数。
还有个问题:去除所有无用数据(大于等于该入栈元素的元素),为什么说这些数据是无用的,去除的这些无用数据有没有可能是之后入栈元素的答案呢?
绝对没有这个可能。
我们来举一个例子:
2 4 5 3 1假如此时3入栈;3左边的4、5是无用数据,都没有存在的必要了,因为3比4和5更小,并且更靠近下一个即将入栈的元素,是比4、5的更优解去除无用数据是为了让更优解入栈,然后2比3小,停下符合条件,最后的栈顶2就是左边第一个小于3的数。
代码:

#include<iostream>
#include<stack>
using namespace std;
const int Max=1e5+10;
int n,arr[Max],ans[Max];
stack<int> stk;
int main()
{cin>>n;for(int i=0;i<n;i++) cin>>arr[i];//arr完整数组for(int i=0;i<n;i++){while(!stk.empty()&&stk.top()>=arr[i]) stk.pop();//把栈内不符合条件的无用数据全部出栈(大于等于该元素),if(!stk.empty()) ans[i]=stk.top();//最后的栈顶就是答案。else ans[i]=-1;stk.push(arr[i]);}for(int i=0;i<n;i++) cout<<ans[i]<<' ';return 0;
}

注:arr数组和ans数组可以不创建,但为了方便统一单调栈的格式,所以创建。

寻找左边第一个比它小的数的下标

与上面的差不多,注意到之前我们寻找的是元素所以让栈去保存元素,现在我们寻找下标,所以让栈去保存元素的下标就可以了

#include<iostream>
#include<stack>
using namespace std;
const int Max=1e5+10;
int n,arr[Max],ans[Max];
stack<int> stk;
int main()
{cin>>n;for(int i=0;i<n;i++) cin>>arr[i];//arr完整数组for(int i=0;i<n;i++){while(!stk.empty()&&arr[stk.top()]>=arr[i]) stk.pop();//把栈内不符合条件的无用数据的下标全部出栈(大于等于该元素),if(!stk.empty()) ans[i]=stk.top();//最后的栈顶就是答案。else ans[i]=-1;stk.push(i);}for(int i=0;i<n;i++) cout<<ans[i]<<' ';return 0;
}

寻找右边第一个小于它的数

寻找右边,我们可以换一种思想,将数组翻转,转换为寻找左边第一个小于自己的数 ,实际上不可能翻转,而是倒序遍历,因此变成了寻找一个数左边第一个小于它的数,于是归结为情形一。

#include<iostream>
#include<stack>
using namespace std;
const int Max=1e5+10;
int n,arr[Max],ans[Max];
stack<int> stk;
int main()
{cin>>n;for(int i=0;i<n;i++) cin>>arr[i];//arr完整数组for(int i=n-1;i>=0;i--){while(!stk.empty()&&stk.top()>=arr[i]) stk.pop();//把栈内不符合条件的无用数据全部出栈(大于等于该元素),if(!stk.empty()) ans[i]=stk.top();//最后的栈顶就是答案。else ans[i]=-1;stk.push(arr[i]);}for(int i=0;i<n;i++) cout<<ans[i]<<' ';return 0;
}

寻找右边第一个小于它的数的下标

P5788 【模板】单调栈
题目描述

给出项数为 n n n 的整数数列 a 1 … n a_{1 \dots n} a1n

定义函数 f ( i ) f(i) f(i) 代表数列中第 i i i 个元素之后第一个大于 a i a_i ai 的元素的下标,即 f ( i ) = min ⁡ i < j ≤ n , a j > a i { j } f(i)=\min_{i<j\leq n, a_j > a_i} \{j\} f(i)=mini<jn,aj>ai{j}。若不存在,则 f ( i ) = 0 f(i)=0 f(i)=0

试求出 f ( 1 … n ) f(1\dots n) f(1n)

输入格式

第一行一个正整数 n n n

第二行 n n n 个正整数 a 1 … n a_{1\dots n} a1n

输出格式

一行 n n n 个整数表示 f ( 1 ) , f ( 2 ) , … , f ( n ) f(1), f(2), \dots, f(n) f(1),f(2),,f(n) 的值。

样例 #1
样例输入 #1

5
1 4 2 3 5

样例输出 #1

2 5 4 5 0

【数据规模与约定】
对于 30 % 30\% 30% 的数据, n ≤ 100 n\leq 100 n100

对于 60 % 60\% 60% 的数据, n ≤ 5 × 1 0 3 n\leq 5 \times 10^3 n5×103

对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 3 × 1 0 6 1 \le n\leq 3\times 10^6 1n3×106 1 ≤ a i ≤ 1 0 9 1\leq a_i\leq 10^9 1ai109
AC代码

#include<iostream>
#include<stack>
using namespace std;
const int Max = 3e6 + 10;int n,arr[Max], ans[Max];
stack<int> stk;int main() 
{cin >> n;for (int i = 1; i <= n; i++) cin >> arr[i];for (int i = n; i>=1; i--){while (!stk.empty() && arr[stk.top()] <= arr[i]) stk.pop();if (!stk.empty()) ans[i] = stk.top();else ans[i]=0;//可不写,ans数组默认初始化全为0stk.push(i);}for (int i = 1; i <= n; i++) cout << ans[i] << ' ';return 0;
}

单调栈总结

①遍历顺序,找某个数的左边就正序遍历,右边就倒序遍历
②及时去掉无用数据的方式,如果找小于该元素,那就出栈中所有大于等于该元素的元素,此时的栈顶就是答案(栈不为空的情况下)。
③栈中存的是元素还是元素下标。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/636802.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity常用的优化技巧集锦

Unity性能优化是面试的时候经常被问道的一些内容&#xff0c;今天给大家分享一些常用的Unity的优化技巧和思路&#xff0c;方便大家遇到问题时候参考与学习。 对啦&#xff01;这里有个游戏开发交流小组里面聚集了一帮热爱学习游戏的零基础小白&#xff0c;也有一些正在从事游…

(C语言)编译和链接

前言͟͟͞͞&#x1f48c;&#xff1a;对于现在的各种编译器而言许多都是好多个功能的集成&#xff0c;那么我们的代码到底是如何去实现的呢&#xff1f;难道我们的计算机可以直接读懂我们所写的代码&#xff0c;并运行吗&#xff1f;对于很多细心的小伙伴们可能会想这样的问题…

Spring Security 优化鉴权注解:自定义鉴权注解的崭新征程

文章目录 1. 引言2. Spring Security基础2.1 Spring Security概述2.2 PreAuthorize注解 3. 自定义鉴权注解的优势3.1 业务语义更明确3.2 参数化鉴权更灵活3.3 可维护性更好 4. 实现自定义鉴权注解4.1 创建自定义注解4.2 实现鉴权逻辑4.3 注册自定义注解和逻辑4.4 使用自定义注解…

Divisibility Problem-codefordes

题目链接&#xff1a;Problem - A - Codeforces 解题思路&#xff1a; 如果 a 能被 b整除&#xff0c;就不需要进行改变&#xff0c;直接输出0&#xff0c;否则输出((a / b) 1) * b - a&#xff0c;找到最小的能被b整除的数。 下面是c代码&#xff1a; #include<iostrea…

探索JAVA神秘运行机制:揭秘JVM内存区域

目录 1. 前文回顾 2.内存区域的划分 2.1 存放类的方法区 2.2 程序计数器 2.3 Java虚拟机栈 2.4 Java堆内存 2.5 其他内存区域 3. 核心内存区域运行流程 4. 总结 1. 前文回顾 上一篇我们一起探索了Java的整体运行流程&#xff0c;类加载器以及类的加载机制&#xff0…

去了字节跳动,才知道年薪 30w 的测试工程师有这么多?

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

会体言一心文-码代-4202

简明版本 最近感悟就是在“常规赛”中&#xff0c;大部分奇技淫巧远不如官方教程。 我使用大模型工具快一年的时间所积累的经验远不如认真看看官方教程。 官方教程 里面有一点就可以秒99%的工具人&#xff0c;“文心一言7*24小时在线&#xff0c;伴你左右。 ” 结合文心一言…

中仕教育:考上选调生之后能不去吗?选调生和公务员哪个比较好?

选调生&#xff0c;是指经过选拔、培训、考核等一系列程序&#xff0c;选拔出的人才。选调生通常需要在基层锻炼一段时间&#xff0c;然后根据工作表现和能力得到提拔。 考上选调生之后能否不去&#xff0c;有以下两种情况。 1.如果通过选调笔试&#xff0c;但是并未参加后续…

2788.按分隔符拆分字符串

前言 力扣还挺上道&#xff08;bushi&#xff09;&#xff0c;今天第一次写每日一题&#xff0c;给了个简单等级的数组题&#xff0c;我只能说&#xff0c;首战告捷&#xff08;小白的呐喊&#xff09;&#xff0c;看看这每日一题我能坚持一天写出来&#xff0c; ok&#xff…

Javaweb之SpringBootWeb案例员工管理之新增员工的详细解析

SpringBootWeb案例 前面我们已经实现了员工信息的条件分页查询以及删除操作。 关于员工管理的功能&#xff0c;还有两个需要实现&#xff1a; 新增员工 修改员工 首先我们先完成"新增员工"的功能开发&#xff0c;再完成"修改员工"的功能开发。而在&q…

Self-RAG:通过自我反思学习检索、生成和批判

论文地址&#xff1a;https://arxiv.org/abs/2310.11511 项目主页&#xff1a;https://selfrag.github.io/ Self-RAG学习检索、生成和批评&#xff0c;以提高 LM 的输出质量和真实性&#xff0c;在六项任务上优于 ChatGPT 和检索增强的 LLama2 Chat。 问题&#xff1a;万能L…

10分钟完成权限系统全流程开发

背景 首先问下chatgpt,权限系统的模型有哪些&#xff1f; 基于上述的结论&#xff0c;我们选择基于角色的访问控制(RBAC)&#xff0c;请从数据库设计、接口文档、代码实现、单元测试四个方面分别详细描述每个部份需要实现的内容。 数据库实现 针对上述的数据库设计部份&#…

格局打开!前端未死,只是要求变高了

本文笔者会从以下几个方面分享&#xff0c;希望能够帮助正在迷茫的前端小伙伴提供一点思路&#xff01; 逛技术博客 不局限框架 全栈工程师兴起 关注前沿 写技术文章 录制前端视频 总结 2024年了&#xff0c;没工作的找到工作了吗&#xff1f;有工作的加薪了吗&#xff1f;加薪…

【高等数学之极限】

一、引言 我们先思考一下&#xff0c;上面三个表达式&#xff0c;是否可以将极限值直接代入求值&#xff0c;我们在计算之前需要先分析一下&#xff0c;如果将极限值代入&#xff0c;那么表达式将会变成什么形式? 经过上面的分析&#xff0c;我们发现第一个式子可以直接带入&a…

浪花 - 主页开发

一、简易版主页 1. 主页展示用户列表 <template><!--推荐用户列表--><van-cardv-for"user in userList":desc"user.profile":title"${user.username}(${user.planetCode})":thumb"user.avatarUrl"><template #…

VUE表单中多个el-upload上传组件共享回调函数解决方案

产品需求界面&#xff1a; 在产品配置页面表单中需要上传多个图片&#xff0c;项目中上传组件采用Element Plus 中的 el-upload&#xff0c;目前问题是每个上传组件都需要实现自己的回调&#xff0c;比如:on-change&#xff0c;采用官方推荐标准代码如下&#xff1a; <el-fo…

java中的时间API

Created: January 19, 2024 2:31 PM 1、旧版时间API的缺点 在java1.0中使用java.utol.Date类表示时间&#xff0c;该类存在很多问题&#xff0c;例如时间零点是1900年1月0日&#xff0c;例如想到表示2024年1月19日就需要如下定义&#xff0c;此外Date也无法表示时间、包含默认…

西瓜书读书笔记整理(十二) —— 第十二章 计算学习理论

第十二章 计算学习理论&#xff08;上&#xff09; 12.1 基础知识12.1.1 什么是计算学习理论&#xff08;computational learning theory&#xff09;12.1.2 什么是独立同分布&#xff08;independent and identically distributed, 简称 i . i . d . i.i.d. i.i.d.&#xff0…

Mysql运维篇(一) 日志类型

一路走来&#xff0c;所有遇到的人&#xff0c;帮助过我的、伤害过我的都是朋友&#xff0c;没有一个是敌人&#xff0c;如有侵权请留言&#xff0c;我及时删除。 一、mysql相关日志 首先&#xff0c;我们能接触到的&#xff0c;一般我们排查慢查询时&#xff0c;会去看慢查询…

一篇文章带你了解 什么是u(ustd)带你了解他的前世今生

在数字货币的繁荣世界中&#xff0c;USDT无疑是其中一位重要的角色。它的前世今生&#xff0c;是一个从无到有&#xff0c;从小到大&#xff0c;经历了种种波折和争议的故事。 2014年11月下旬&#xff0c;一个名为Realcoin的注册地为马恩岛和香港的公司决定改变自己的名字&…