算法总结——单调栈

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

文章目录

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

一、单调栈的定义

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

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

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

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

二、单调栈的应用:

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

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

题目练习: 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.hqwc.cn/news/418405.html

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

相关文章

组件通信方式

组件通信 父子组件通信 单向数据流 属性传递props&#xff08;还有插槽&#xff0c;$attrs非属性&#xff09;/$emit&#xff0c;发布订阅模式 方法也可以作为属性 父子组件渲染生命周期&#xff1a; 获取组件实例。$children、ref&$refs/$parent 祖先和后代 组件和后代通信…

css绘制下拉框头部三角(分实心/空心)

1:需求图: 手绘下拉框 带三角 2:网上查了一些例子,但都是实心的, 可参考,如图: (原链接: https://blog.csdn.net/qq_33463449/article/details/113375804) 3:简洁版的: a: 实心: <view class"angle"/>.angle{width:0;height:0;border-left: 10px solid t…

HTTP 协议和 TCP/IP 协议之间有什么区别?

HTTP&#xff08;超文本传输协议&#xff09;和TCP/IP&#xff08;传输控制协议/互联网协议&#xff09;是两种在互联网通信中广泛使用的协议&#xff0c;它们之间的区别和联系对许多人来说可能还不是很清晰&#xff0c;今天我们就带大家来一起了解一下HTTP和TCP/IP协议这2者之…

一篇文章说清楚TVS管

大家好,这里是大话硬件。 这篇文章就分享总结TVS相关内容。 1.TVS管介绍 瞬变电压抑制二极管也被称为TVS管,英文名Transient Voltage Suppression,从TVS的中文名可以看出,TVS管对电压的响应速度比较快,而且能够抑制电压的变化,且属于二极管中的一种器件。因此,TVS管会…

Python初识——小小爬虫

一、找到网页端url 打开浏览器&#xff0c;打开百度官方网页点击图片&#xff0c;打开百度图片 鼠标齿轮向下滑&#xff0c;点击宠物图片 进入宠物图片网页&#xff0c;在网页空白处点击鼠标右键&#xff0c;弹出的框中最下方显示“检查”选项&#xff0c;点击&#xff08;我是…

记录一下uniapp 集成腾讯im特别卡(未解决)

uniapp的项目运行在微信小程序 , 安卓 , ios手机三端 , 之前这个项目集成过im,不过版本太老了,0.x的版本, 现在需要添加客服功能,所以就升级了 由于是二开 , 也为了方便 , 沿用之前的webview嵌套腾讯IM的方案 , 选用uniapp集成ui ,升级之后所有安卓用户反馈点击进去特别卡,几…

数据库的内连接和外连接

数据库的内连接和外连接 内连接: 两个或两个以上的表进行关联查询时&#xff0c;查询的结果集中 返回所有满足连接条件的行。 外连接: 两个或两个以上的表进行关联查询时&#xff0c;查询的结果集中 除了返回满足连接条件的行以外&#xff0c;还返回左&#xff08;或右&…

搭建网站website

一.领取一个免费域名和SSL证书&#xff0c;和CDN 特点&#xff1a;支持Cloudflare CDN Cloudflare是全球知名的CDN提供商&#xff0c;如果你不想暴露你的源站&#xff0c;又想使用我们的二级域名&#xff0c;不需要前往Cloudflare添加域名&#xff0c;修改NS记录&#xff0c;…

使用Element中的input组件如何实现文字和输入框在一行显示

利用 <el-form-item label"商品名称&#xff1a;">标签包裹即可&#xff0c;label写提示文字 <el-form ref"form" label-width"100px"><el-form-item label"商品名称&#xff1a;"><el-input v-model"na…

CentOS7 修改主机名

目录 主机名分类静态主机名 (Static hostname)瞬态主机名 (Transient hostname)漂亮主机名 (Pretty hostname)查看主机名 修改主机名使用 hostnamectl 命令临时有效永久生效 编辑配置文件 主机名分类 在CentOS7和其他使用systemd的现代Linux发行版中&#xff0c;有三种不同类型…

Javascript简介(全部是基础)

js初识 js是一种解释性语言&#xff0c;不需要编译&#xff0c;直接由浏览器解析执行 组成 ECMAScript是一种开放的&#xff0c;被国际上广为接收的&#xff0c;标准的脚本语言规范&#xff0c;主要描述&#xff1a;语法&#xff0c;变量&#xff0c;数据类型&#xff0c;运算…

ubuntu-20.04.6-live-server-amd64安装教程-完整版

简介 Ubuntu 20.04.6 Live Server AMD64 安装教程 - 完整版" 提供了详细的指南&#xff0c;旨在帮助用户在使用 AMD64 架构的服务器上安装 Ubuntu 20.04.6 Live Server 版本。该教程包含全面的步骤和详细说明&#xff0c;使用户能够顺利完成整个安装过程&#xff0c;建立…