数据结构之单调栈(含代码实现)

article/2025/9/30 22:09:16

目录

1.单调栈的基本概念 :

2.单调栈的应用

2.1单调栈

 2.2单调栈进阶

 2.3最大矩形面积

 2.4最大矩形

2.5统计全为1的子矩阵数量


1.单调栈的基本概念 :

相信大家对栈都非常的熟悉?栈有一个非常鲜明的特点:先进后出

而所谓 单调栈 则是在栈的 先进后出 基础之上额外添加一个特性:从栈顶到栈底的元素是严格递增(or递减)。

1.对于单调递增栈,若当前进栈元素为 x,从栈顶开始遍历元素,把大于等于x的元素弹出栈,直接遇到一个小于 x 的元素或者栈为空为止,然x压入栈中。

2.对于单调递减栈,则每次弹出的是小于 x的元素。

以单调递增为例:

以序列:3,2,4,0,1,5

让其从左到右依次入栈:

第一步:将3压入栈中。

第二步:2入栈时发现此时栈中的元素比自己要大将栈中元素3弹出,将2入栈。

第三步:4入栈往栈的顶部看了一下栈顶的元素比我要小,好的4你可以到栈里面去。

第四步:0入栈往栈顶一看好家伙你居然比我要大,好的你可以从栈里面出去了,2,4出栈0入栈

第五步:1入栈往栈的顶部看了一下栈顶的元素比我要小,好的1你可以到栈里面去.

第六步:5入栈往栈的顶部看了一下栈顶的元素比我要小,好的5你可以到栈里面去.

2.单调栈的应用

2.1单调栈

单调栈结构_牛客题霸_牛客网 (nowcoder.com)

题目描述:

分析:

1.要找到数组中每个i位置左边和右边离i最近的且比arr[i]的值要小的位置。我们可以使用单调栈来实现:栈中保存数组的下标

且保证从栈底到栈顶元素下标对应元素是从小到大的.如果我们是要找到数组中每一个 i 位置左边和右边离 i 位置最近且值比 arr[i] 大的位置,那么我们就需要保持栈从栈顶到栈底的下标i所对应的arr[i]由小到大的特性。

下面我们使用单调栈来求得数组arr[3 4 1 5 6 2 7]中每一个 i 位置左边和右边离 i 位置最近且值比 arr[i] 小的位置:

1.当数组遍历到0号小标时,发现栈为空,直接将下标0压入栈中即可,如下图所示:

在这里插入图片描述

 2.当数组遍历到1号下标时发现栈顶元素3比我小,好的你可以进栈了,1进栈.如图所示:

在这里插入图片描述

 3.接着数组遍历到2号位时,发现arr[2]小于栈顶元素即数组的下标1所对应的数组元素4,那么直接弹出栈顶元素1,那么 1 位置左边和右边离 1 位置最近且值比 arr[1] 小的位置分别为新的栈顶0和当前的i = 2,并记录在结果数组中。若当前位置i其左边和右边不存在比arr[i]最近且比其小元素,那么就默认位置为-1。重复上述过程,直到栈为空或者arr[2]大于栈顶下标所对应的数组元素时直接压入i。该过程的结果如图所示

在这里插入图片描述

 当数组从3号位遍历到4号时,会将数组元素5和6所对应的下标压入栈中。如下图示:

在这里插入图片描述

 当数组遍历到5号位时,发现arr[5]小于栈顶中元素4所对应的数组元素6,那么弹出栈顶元素并记录其左右比arr[5]小且最近的元素的下标。同理,新的栈顶也会被弹出。然后压入元素2的下标5。如下图示:

在这里插入图片描述

 当数组遍历到6号位时,发现arr[6]大于栈顶元素5所对应的arr[5],即2,那么直接压入下标6。自此,数组遍历结束,栈和结果数组的状态如下图所示:

在这里插入图片描述

 接着,我们循环的弹出栈顶元素idx,并记录arr[idx]的左边和右边最近且比arr[idx]小的元素下标,我们可以发现左边最近且比arr[idx]小的元素的下标为新的栈顶元素(若不存在则为-1),右边最近且比arr[idx]小的元素的下标不存在,即为-1。最后我们得到如下结果数组:

在这里插入图片描述

对应代码:

#include<iostream>
#include<stack>
#include<vector>
using namespace std;
int main(){int n;cin>>n;vector<int>arr(n);for(int i=0;i<n;i++){cin>>arr[i];}stack<int>stk;vector<vector<int>>ans(n,vector<int>(2));//保存答案for(int i=0;i<n;i++){while(!stk.empty()&&arr[stk.top()]>arr[i]){int cur=stk.top();stk.pop();int leftIndex=stk.empty()?-1:stk.top();//左边比它小的//右边比它小的就是当前的下标ians[cur][0]=leftIndex;ans[cur][1]=i;}stk.push(i); }while(!stk.empty()){int rightIndex=-1;//结算阶段右边一定没有比当前数要小的int cur=stk.top();//当前位置可以结算stk.pop();int leftIndex=stk.empty()?-1:stk.top();//看当前位置下面是否还压着数ans[cur][0]=leftIndex;ans[cur][1]=rightIndex;}//打印结果for(int i=0;i<n;i++){printf("%d %d\n",ans[i][0],ans[i][1]);}}

 2.2单调栈进阶

单调栈结构(进阶)_牛客题霸_牛客网 (nowcoder.com)

题目描述:

分析本题和上题不一样的是有重复的元素:那么就有可能入栈的元素和当前栈顶的元素相等那么当前栈顶元素右边比它小的无法正确的结算。

所以此时我们需要对单调栈的结构进行改造:将栈中存在下标的整型改成链表单调性依然不变。下面以这组数据为例:数组arr[2,2,4,4,3]

1.当遍历到数组的0号位时,由于栈为空,那么将下标0添加进链表中,然后将链表压入栈中;

当遍历到数组的1号位时,发现栈不为空,且因为arr[1]等于链表元素0所对应的arr[0],即都等于2,那么则将下标1添加进栈顶的链表中,如下图示:

在这里插入图片描述

2.接着数组遍历到2号时,发现arr[2]大于栈顶链表元素0所对应的arr[0],那么直接将下标2添加进新的链表中,然后将链表压入栈中。另外,当数组遍历到3号位时同当遍历到数组的1号位时的情况一样,如下图示:

                                         

在这里插入图片描述

当数组遍历到4号位时,发现arr[4]小于栈顶链表元素2所对应的arr[2],那么则将栈顶的链表弹出,然后遍历该链表,我们可以知道链表中的每个元素idx所对应的arr[idx]的左边和右边离其最近且小于arr[idx]的元素的下标分别为新的栈顶链表最右的元素(这里是3)和当前遍历到的数组元素的下标(这里是4)。然后重复上述过程,直到arr[4]小于或等于新的栈顶链表元素idx所对应的arr[idx]才停止。

如果arr[4]等于新的栈顶链表元素idx所对应的arr[idx]时,那么就将arr[4]添加到该栈顶链表中,否则就将arr[4]添加进新的链表,然后再将该链表添加进栈中。由于arr[4]不等于栈顶链表元素2所对应的arr[2],那么我们将arr[4]添加进新的链表,然后再将该链表添加进栈中。如图所示:

在这里插入图片描述

 接下来我们开始循环的弹出栈中的链表,然后遍历该链表,我们可以知道链表元素4所对应的arr[4]的左边离其最近且小于arr[4]的元素的下标为新的栈顶链表最右的元素的下标1,右边的则不存在,默认为-1。

在这里插入图片描述

我们继续弹出栈顶链表,然后遍历该链表。我们发现无新的栈顶,那么我们说明当前链表元素idx所对应的arr[idx]左边无离其最近且小于arr[idx]的元素,我们默认下标为-1,而右边的也不存在,默认为-1。

在这里插入图片描述

 对应代码:

#include<iostream>
#include<vector>
#include<stack>
#include<list>
using namespace std;
int main(){int n;cin>>n;vector<vector<int>>ans(n,vector<int>(2));//保存答案vector<int>arr(n);stack<list<int>>stk;//栈中存储链表for(int i=0;i<n;i++){cin>>arr[i];while(!stk.empty()&&arr[i]<arr[stk.top().front()]){//不满足单调性list<int>tmp=stk.top();//当前节点可以结算了stk.pop();int leftIndex=stk.empty()?-1:stk.top().back();//左边比他小的元素下标for(auto x:tmp){ans[x][0]=leftIndex;ans[x][1]=i;//右边比他小的下标对应的元素就是当前的i}}if(!stk.empty()&&arr[stk.top().front()]==arr[i]){//如果相等加入栈顶元素的链表中stk.top().push_back(i);}else{stk.push({i});}}while(!stk.empty()){//结算阶段右边比他小的没有了list<int>tmp=stk.top();stk.pop();int leftIndex=stk.empty()?-1:stk.top().back();for(auto x:tmp){ans[x][0]=leftIndex;ans[x][1]=-1;}}//输出结果for(int i=0;i<n;i++){printf("%d %d\n",ans[i][0],ans[i][1]);}}

 2.3最大矩形面积

剑指 Offer II 039. 直方图最大矩形面积 - 力扣(LeetCode) (leetcode-cn.com)

题目描述:

 解题思路:

本题的解题思路:

利用单调栈,求每个柱子作为矩形高度时的面积,取最大。

即要找到左右两侧比该柱子矮的两个柱子的坐标(最近的),面积为两侧矮柱子之间的宽度乘以该柱子的高度。

如图,柱子B两侧比它矮的最近的柱子为柱A、C,则以柱子B为矩形高度时的面积为

(C坐标-A坐标-1) * B高度

特殊情况:

1、所求柱子左侧无比它矮的,则计算宽度时,将上述公式的 A坐标 改为 -1 计算即可
2、所求柱子右侧无比它矮的,则计算宽度时,将上述公式的 C坐标 改为 heights.length 计算

对应代码:

class Solution {
public:int largestRectangleArea(vector<int>& arr) {stack<int>stk;int Maxans=0;//记录答案for(int i=0;i<arr.size();i++){while(!stk.empty()&&arr[i]<=arr[stk.top()]){//等于int cur=stk.top();stk.pop();int leftIndex=stk.empty()?-1:stk.top();//看自己底下是否压着东西int curArea=(i-leftIndex-1)*arr[cur];maxans=max(maxans,curArea);//看是否需要更新答案}stk.push(i);}//结算阶段while(!stk.empty()){int j=stk.top();stk.pop();int leftIndex=stk.empty()?-1:stk.top();int curArea=(arr.size()-leftIndex-1)*arr[j];//当前的面积maxans=max(maxAns,curArea);}return maxans;}
};

注意这里如果遇到相等的元素可以将其从栈中弹出虽然弹出的结果没用算对但是进来了一个和它一样的只要它算对了即可。

 2.4最大矩形

85. 最大矩形 - 力扣(LeetCode) (leetcode-cn.com)

 题目描述:

 分析:

暴力枚举:枚举每个左上角和每个右下角,复杂度是O(n^2)*O(n^2),再枚举这个区域内是否存在0,复杂度为O(n^2),总共的时间复杂度为O(n^6)。过不了的

方法二单调栈:

我们可以枚举:子矩阵必须以第0行做地基的情况下那个子矩阵包含的1最多,在枚举子矩阵必须以第1行做地基的情况下那个子矩阵包含的1最多,重复上述过程答案一定在其中

2.5统计全为1的子矩阵数量

1504. 统计全 1 子矩形 - 力扣(LeetCode) (leetcode-cn.com)

 

解题思路:

参考上一题的解题思路,必须以第0行做地基的情况下子矩阵全为1的个数,必须以第1行做地基的情况下子矩阵全为1的个数.重复上述过程最后不就求出来了吗?利用的技巧和上题一样空间压缩。

假设我们将其压缩为一个这样的直方图我们要求其子矩阵全为1的数量,首先对于b来说现找到左边比它小的和右边比它小的一共有多少个呢?假设5到6之间的长度为n一共有(n*(n+1)/2)*(5-2)个。为什么不是减1呢那是因为他之前已经算过了。

对应代码:

class Solution {
public:int numSubmat(vector<vector<int>>& mat) {int res=0;vector<int>height(mat[0].size());for(int i=0;i<mat.size();i++){for(int j=0;j<mat[0].size();j++){//枚举第i行做地基子矩阵组中全为1的个数height[j]=mat[i][j]==0?0:height[j]+1;}res+=countFromBottom(height);}return res;}int countFromBottom(vector<int>&height){int nums=0;stack<int>stk;for(int i=0;i<height.size();i++){while(!stk.empty()&&height[stk.top()]>=height[i]){int cur=stk.top();stk.pop();if(height[cur]>height[i]){//如果和你相等后面再算法int leftIndex=stk.empty()?-1:stk.top();int n=i-leftIndex-1;int down=max(leftIndex==-1?0:height[leftIndex],height[i]);//取两者中最小的一个后面同一算//最小的那个的时候统一算nums+=num(n)*(height[cur]-down);//总的方法}}stk.push(i);}while(!stk.empty()){int cur=stk.top();stk.pop();int leftIndex=stk.empty()?-1:stk.top();int n=height.size()-leftIndex-1;int down=leftIndex==-1?0:height[leftIndex];nums+=(height[cur]-down)*num(n);}return nums;}int num(int n){//根据数学方法计算 1+2+....nreturn (n*(n+1))>>1;}
};


http://chatgpt.dhexx.cn/article/kR29YrDu.shtml

相关文章

C++之单调栈

单调栈的性质 单调栈是一种特殊的栈&#xff0c;特殊之处在于栈内的元素都保持一个单调性。 假设下图是一个栈内元素的排列情况(单调递增的栈)&#xff1a; 此时插入情况有两种&#xff1a; &#xff08;1&#xff09;插入元素大于栈顶元素&#xff1a; 因为7 > 6&#xf…

单调栈以及单调栈的应用

文章目录 单调栈的概念单调栈的应用CD101 单调栈结构&#xff08;无重复值&#xff09;CD188 单调栈结构(有重复值)496. 下一个更大元素 I739. 每日温度1856. 子数组最小乘积的最大值84. 柱状图中最大的矩形85. 最大矩形1504. 统计全 1 子矩形907. 子数组的最小值之和1307 验证…

单调栈完全解析

目录 单调栈的应用场景 为什么要使用单调栈&#xff1f; 单调栈作用的基本过程 单调栈的实现方式 栈里面的元素存放数字下标&#xff08;无重复元素&#xff09; 栈里面的元素存放数字下标组成的链表 &#xff08;有重复元素&#xff09; 单调栈的应用题目 直方图类型 …

单调栈与单调队列

文章目录 单调栈与单调队列一、单调栈1.单调递增栈2.单调递减栈总结 二、单调队列(单调双端队列) 单调栈与单调队列总结&#xff1a; 单调栈与单调队列 单调栈就是栈内元素满足单调性的栈结构。此处的单调性分为单调递增与单调递减 如何维护一个单调栈&#xff1a; 单调递增栈…

详解单调栈算法

前言 嘿&#xff01;彩蛋&#xff01;感觉有帮助就三连呗&#xff01; 如果你对这篇文章可感兴趣&#xff0c;可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」&#xff0c;查看完整博客分类与对应链接。 栈属于基础数据结构之一&#xff0c;基础到仅用「后进…

单调栈

定义&#xff1a; 单调栈&#xff0c;顾名思义就是栈内元素单调按照递增(递减)顺序排列的栈。 单调递增栈&#xff1a; ①在一个队列中针对每一个元素从它右边寻找第一个比它小的元素 ②在一个队列中针对每一个元素从它左边寻找第一个比它小的元素 单调递减栈&#xff1a; …

单调栈(C/C++)

目录 1. 单调栈的定义 2. 单调栈的常见用途 3. 案例分析 3.1 暴力解法 3.2 单调栈 4. 单调栈总结 1. 单调栈的定义 单调栈顾名思义&#xff0c;就是栈内的元素是单调的。根据栈内元素的单调性的不同&#xff0c;可以分为&#xff1a; 单调递增栈&#xff1a;栈内元素是单…

【算法】单调栈

目录 单调栈的定义&#xff1a;伪代码&#xff1a;应用1.模板题2.视野总和问题3.柱状图中的最大矩形4.最大区间 碎碎念&#xff1a; 单调栈的定义&#xff1a; 从名字上就能猜出来&#xff0c;这种数据结构在栈的基础上&#xff0c;栈内的元素是单调有序的&#xff0c;所以单调…

单调栈详解

定义&#xff1a; 单调栈&#xff0c;顾名思义就是栈内元素单调按照递增(递减)顺序排列的栈。 适用问题&#xff1a; 要知道单调栈的适用于解决什么样的问题&#xff0c;我们首先需要知道单调栈的作用。单调栈分为单调递增栈和单调递减栈&#xff0c;通过使用单调栈我们可以访…

[数据结构]——单调栈

单调栈 笔者在做leetcode的题(下一个出现的最大数字)时&#xff0c;接触到了单调栈这一种数据结构&#xff0c;经过研究之后&#xff0c;发现单调栈在解决某些问题时出奇的好用&#xff0c;下面是对单调栈的性质和一些典型题目。 什么是单调栈&#xff1f; 从名字上就听的出…

scp出现错误的解决办法

scp往远程主机上传送rmandata时报了如下错误&#xff1a; (看不见的放大) 我的做法是&#xff1a; 在执行scp的主机提示的scp目录 vi ~/.ssh/known_hosts 删除我红色部分遮盖的主机ip那一行&#xff0c;故障解决 来自 “ ITPUB博客 ” &#xff0c;链接&#xff1a;http://blo…

关于 fatal error LNK1158: 无法运行“rc.exe” 的解决方法

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://blog.csdn.net/qq21497936/article/details/110680001 各位读者&#xff0c;知识无穷而人力有穷&#xff0c;要么改需求&#xff0c;要么找专业人士&#xff0c;要么自己研究 红胖子(红模仿…

VS发生RC1107错误的原因

最近MFC程序中&#xff0c;用VS的资源编辑打开时&#xff0c;老是发生 fatal error RC1107: invalid usage; use RC /? for Help 这种错误&#xff0c;记得前几天解决过一次&#xff0c;但是当时忘了怎么解决的了。今天每建一个新的工程都遇到这个问题&#xff0c;郁闷坏了&a…

错误 LINK : fatal error LNK1158: 无法运行“rc.exe”

2019独角兽企业重金招聘Python工程师标准>>> 问题 软件环境&#xff1a;Windows 10 Pro Visual Studio 2015 然后安装了 Windows 10 SDK Windows 10 SDK 是用这个 ISO 文件安装的&#xff1a;17134.12.180419-0858.rs4_release_svc_prod2_WindowsSDK.iso 在 Visual…

[rtsp @ 0x55ba1dae9200] UDP timeout, retrying with TCP的解决办法

使用ffmpeg 进行解码rtsp的时候出现: [rtsp @ 0x55ba1dae9200] UDP timeout, retrying with TCP如下所示: 需要使用接口指定以下tcp连接就可以解决了。 具体的代码如下: // rtsp:tcpAVDictionary* options = NULL;av_dict_set(&options, "rtsp_transport", …

brpc源码解析(二)—— brpc收到请求的处理过程

文章目录 一、基本设计思路二、实现细节三、总结 作为rpc服务器&#xff0c;在启动过后&#xff0c;最主要的一个过程就是收到请求后的处理&#xff0c;而这就牵涉到一个网络编程相关最基本的部分&#xff1a;如何有效地处理socket传过来地数据&#xff0c;这篇文章就来详细聊一…

libpng warning iCCP 错误处理方法

png图片缺乏某些库&#xff0c;导致损坏&#xff0c;或者多余了一些数据会导致以下报错&#xff1a; libpng warning: iCCP: known incorrect sRGB profile libpng warning iccp extra compressed data一些可能的解决方案&#xff1a; 已有方案 来自&#xff1a;https://blo…

创建线程提示SCB_CFSR_BFSR:0x04 IMPRECISERR 错误

在RTthread的编程出现了问题 这种问题并不是系统出现的问题&#xff0c;而是在处理自己的函数内部出现了数组越界&#xff0c;内存出现错误导致的。 关键还是自己指针访问的非法访问导致这些问题。遇到问题还是要多检查自己写的接口问题。

无法运行rc.exe(已解决)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言原因解决一&#xff1a;在C:\Program Files (x86)下搜索rc.exe二&#xff1a;右击&#xff0c;打开找到的rc.exe的文件夹然后进入Visual Studio 2019 前言 解决…

RTCP(一): RR--Receiver Reports 接收者报告

RTCP RR的格式 接受者报告的RTCP类型是201&#xff0c;如图1.1所示。 图1.1 reporter ssrc rr报告发送者的ssrc&#xff0c;也就是rtp报文接受者自己的ssrc. reportee ssrc rr报告接受者的ssrc&#xff0c;也就是rtp报文发送者的ssrc. cumulative number of packet lo…