单调栈详解

article/2025/9/30 22:45:47

定义:

 单调栈,顾名思义就是栈内元素单调按照递增(递减)顺序排列的栈。

适用问题:

要知道单调栈的适用于解决什么样的问题,我们首先需要知道单调栈的作用。单调栈分为单调递增栈和单调递减栈,通过使用单调栈我们可以访问到下一个比他大(小)的元素(或者说可以)。也就是说在队列或数组中,我们需要通过比较前后元素的大小关系来解决问题时我们通常使用单调栈。下面我们通过简单介绍单调减栈和单调增栈问题来进一步说明使用单调栈处理问题的过程。

单调栈主要解决以下问题:寻找下一个更大元素
寻找前一个更大元素
寻找下一个更小元素
寻找前一个更小元素
qualified 的 窗口的 max/min
sliding window max/min

首先说一下单调递减栈

单调递减栈: ①在一个队列中针对每一个元素从它右边寻找第一个比它大的元素②在一个队列中针对每一个元素从它左边寻找第一个比它大的元素(从后往前遍历)

其实 1 和 2 只是从两个不同的角度来分析问题,但是它解决问题的过程是一样的。并且在寻找的过程中我们都需要对栈内的元素进行一定的处理。我们通过介绍一个简单的例子来说明这个问题。
假如有一个地方三月三十一号要传授武林秘籍,大家要在这一天之前来这里排好队等3月31号能够学到东西。来了很多武林高手,但是这个地方的人要根据人来的先后顺序教,先来的学的武功就高深,来的越靠后学的就越差,但是能保证只要来就能学到。假如他们一开始有一个初始的排队顺序和武力水平如下所示,大家可以按照这个初始顺序从前往后学习。

但是问题来了,武力值高的肯定愿意先学习到高深一点的武功啊,那我就找到前面武力值低的人说:“你别学了,我叫你一门武功比那里学的好”,武力值低的那一个一听就答应了,从它哪里学了一门武术高高兴兴的走了,这样武功高的那个就排在了前面。下面我们来从前往后模拟一下过程:

在这里插入图片描述 (1)首先来的是炮灰甲,炮灰甲一看,OK,栈内没有人就可以先排在第一位
(2)然后扫地僧来了,扫地僧教给了炮灰甲自己的功夫(炮灰甲的师傅为扫地僧),然后扫地僧让炮灰甲离开自己排在第一位
(3)然后杨过过来,看到前面是扫地僧,自己打不过只能老老实实站到队里
(4)后面一直来人(如3)------ 直到张三 对里面站的是   扫地僧 杨过 慕容复 张三   
(5)然后张无忌来了,他首先看到前面是张三,张无忌教给张三自己的功夫并让他离开(张三师傅为张无忌),
        然后张无忌再往前看到了慕容复(过程如上所示)------------直到遇到扫地僧,OK,自己打不过,
        老老实实的占到了后面      —现在队里有扫地僧,张无忌     ----杨过,慕容复,张三的师傅为张无忌
(6)柯镇恶来了,打不过,站到后面就好了
(7)然后乔峰来了把柯镇恶和张无忌打发走,   —现在队里有扫地僧,乔峰     ----张无忌,柯镇恶的师傅为乔峰
(8)然后李四来了,打不过乔峰,只能站到了最后。
(9)第二天扫地僧,乔峰,李四成功学到了武林秘籍
现在看他们分别学到武功对应如下图:

在这里插入图片描述
这样我们就处理完了所有过程。因此单调栈的时间复杂度为O(n),在比较时对出栈的元素有一个处理(例如扫地僧让炮灰甲走的时候教给他自己的武功),另外最后留在站内的元素有一个统一的处理(扫地僧,乔峰,李四成功学到了武林秘籍)。

 伪代码如下所示:

对于第i个到来的人:每当队里面有人并且打不过自己的时候:让这个人离开并交给他自己的武功自己入队     

 下面看一道用单调递减栈解决的问题:

1019. 链表中的下一个更大节点

给出一个以头节点 head 作为第一个节点的链表。链表中的节点分别编号为:node_1, node_2, node_3, … 。

每个节点都可能有下一个更大值(next larger value):对于 node_i,如果其 next_larger(node_i) 是 node_j.val,那么就有 j > i 且 node_j.val > node_i.val,而 j 是可能的选项中最小的那个。如果不存在这样的 j,那么下一个更大值为 0 。

返回整数答案数组 answer,其中 answer[i] = next_larger(node_{i+1}) 。

注意:在下面的示例中,诸如 [2,1,5] 这样的输入(不是输出)是链表的序列化表示,其头节点的值为 2,第二个节点值为 1,第三个节点值为 5 。

示例:

输入:[2,1,5]
输出:[5,5,0]

输入:[2,7,4,3,5]
输出:[7,0,5,5,0]


输入:[1,7,5,1,9,2,5,1]
输出:[7,9,9,9,0,5,0,0]

代码:

过程如下:先用一个数组将链表内元素存储起来,然后使用单调递减栈。从前往后遍历元素,遍历到每个元素的位置的时候,判断栈是否为空,若不为空,判断栈顶元素位置的值是否小于当前元素,若小于栈顶元素位置则将他的位置直接入栈,若大于栈顶元素位置的值,则栈顶元素出栈并且将栈顶元素的位置的值设置为当前元素的值。(有点绕口,栈里面存的不是元素的大小,存的是元素的位置)

代码:

class Solution:def nextLargerNodes(self, head: ListNode) -> List[int]:stack = []nums = []i=0temp = headwhile temp != None:nums.append(temp.val)temp = temp.nexti += 1res = [0]*itemp = headfor j in range(len(nums)): while len(stack)>0 and nums[j]>nums[stack[-1]]:i = stack.pop()res[i] = nums[j]stack.append(j)return res


下面来说一下单调递增栈

单调递增栈: ①在一个队列中针对每一个元素从它右边寻找第一个比它小的元素②在一个队列中针对每一个元素从它左边寻找第一个比它小的元素(从后往前遍历)

单调递增栈和单调递减栈相类似,只是存入的方式不同,因此不做过多的介绍,通过一个例题来介绍:

896. 单调数列

如果数组是单调递增或单调递减的,那么它是单调的。

如果对于所有 i <= j,A[i] <= A[j],那么数组 A 是单调递增的。 如果对于所有 i <= j,A[i]> = A[j],那么数组 A 是单调递减的。

当给定的数组 A 是单调数组时返回 true,否则返回 false。

示例 1:
输入:[1,2,2,3]
输出:true

示例 2:
输入:[6,5,4,4]
输出:true

示例 3:
输入:[1,3,2]
输出:false

示例 4:
输入:[1,2,4,5]
输出:true

示例 5:
输入:[1,1,1]
输出:true
过程如下:在遍历过程中使用单调递增数列和单调递减数列,依次存入最小栈和最大栈,如果数组是单调的,那么单调递减栈或单调递增栈中必定有一个是没有弹出的过程,也就是说必定有一个栈是满的。最后判断栈的长度,如果两个有一个长度和数组A的长度相同的,则满足条件。

代码如下

class Solution:def isMonotonic(self, A: List[int]) -> bool:st_up = []st_down = []for i in range(len(A)):while len(st_down)>0 and A[i]>A[st_down[-1]]:st_down.pop()while len(st_up)>0 and A[i]<A[st_up[-1]]:st_up.pop()st_down.append(i)st_up.append(i)if len(st_up)==len(A) or len(st_down)==len(A):return Truereturn False


将单调栈的基础作用介绍完,我们介绍一下单调栈在实际问题中的应用

84. 柱状图中最大的矩形

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。

在这里插入图片描述

– - - - - - 

以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。

在这里插入图片描述
---------- 

图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。

输入: [2,1,5,6,2,3]
输出: 10
看到这个题我们首先想到的是暴力法,我们首先遍历每个柱状图,针对每个柱子,我们遍历链表,分别找到左边第一个比它小的柱子位置x和右边第一个比它小的柱子y,这样在第i个柱子的最大面积为 heights[i] * (y-x-1),这样我们再遍历所有柱子的最大面积,找出最大的那个即可得到最大矩形面积。 时间复杂度O(n^2),空间复杂度O(n)。

如图所示,我们遍历到第五个柱子的时候, x=1, y=6 则 S[4] = 8

在这里插入图片描述
---------- 

但是当数据量很大的时候,暴力法显然是不可取的。我们考虑使用单调递增栈来解决问题。

过程如下:
首先我们结合暴力法来分析一下为什么使用单调栈做本题,我们计算每个柱子出的最大面积的时候是分别找到左边第一个比它小的柱子位置x和右边第一个比它小的柱子y来计算的。当我们弹出一个元素的时候,因为是单调递增栈因此栈顶元素值一定是它左边第一个小于它的元素,而**使它弹出的那个元素一定是它右边第一个小于它的值**,这样我们只需要使用弹出的高度 * 宽度就可计算出当前位置的面积,如弹出 2 的时候如下图所示:

在这里插入图片描述
做题之前我们来对柱状图做一个简单的处理,即在最前面和最后面分别加一个高度为零的柱形图如下图所示,因为我们求的是面积,因此添加这两个高度为零的柱形对我们的结果并不会产生影响。
-----------

在这里插入图片描述

我们来分析一下加这两个值的原因,首先分析最后那个0,因为在遍历的过程中只有满足一定的条件(即栈不为空且栈顶元素大于当前位置元素的时候)才会出栈并计算出栈元素索引位置的最大面积。但是遍历完所有的元素栈内一定会有残留,这样的话就不能求出所有位置的最大面积(只有弹出的时候才计算弹出位置的面积),因此在最后一位加0是为了保证所有的非零元素都能弹出栈。那我们为什么在首位加0呢,其实没有什么特殊的原因,就是为了保证栈不为空,这样计算方便一点。

代码如下:

class Solution:def largestRectangleArea(self, heights) -> int:heights.append(0)S = [0] * len(heights)stack = [[0, -1]]for i in range(len(heights)):while stack and stack[-1][0] > heights[i]:last_height, last_index = stack.pop()S[last_index] = last_height * (i - stack[-1][1]-1)stack.append([heights[i], i])return max(S)


单调递减栈的实际应用:

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

在这里插入图片描述
------------------
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 

示例:

输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6
和上面用单调递增栈解决问题思路相类似。如果用暴力法解决一定是针对每个柱子,找后一个比它大的,然后计算他们两个中间能接多少雨水,最后总和就可以了。但这需要O(n^2)的时间复杂度。如果我们用单调递减栈存储。那么当一个索引位置弹出的时候,栈顶元素(如果存在)一定是左比它大的,使他弹出的遍历到的那个元素一定是右边第一个比它大的。这样我们就可以计算弹出的那个元素的前一个位置和遍历位置中间接多少雨水了。如当第三个位置弹出时如下图:

--------------

在这里插入图片描述

代码如下:

class Solution:def trap(self, height):lenh = len(height)if lenh<3:return 0st = []res = 0for i in range(len(height)):while st and height[i]>height[st[-1]]:j = st.pop()if st:res = res + (min(height[st[-1]], height[i]) - height[j]) * (i-st[-1]-1)st.append(i)return res
class Solution {public int trap(int[] height) {if(height==null || height.length == 0) return 0;Stack<Integer> st = new Stack<>();int l=0, r=0, res=0;for(int i=0; i<height.length; i++){while(!st.isEmpty() && height[st.peek()] < height[i]){int temp = st.pop();if(!st.isEmpty()){res += (Math.min(height[i], height[st.peek()])- height[temp]) * (i - st.peek() - 1);}}st.push(i);}return res;}
}

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

相关文章

[数据结构]——单调栈

单调栈 笔者在做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…

MFC生成错误msado15.tlh(3991):fatal error C1003: 错误计数超过100;正在停止编译

MFC生成过程产生错误msado15.tlh(3991): fatal error C1003: 错误计数超过 100&#xff1b;正在停止编译 1 问题描述 在MFC生成解决方案过程中&#xff0c;当点击工具栏的生成按钮时&#xff0c; 会出现编译错误的情况&#xff1a; msado15.tlh(3991): fatal error C1003: 错…

webrtc编译中的错误解决

webrtc编译记录 错误1&#xff1a;该错误的意思是python的安装路径要和你此时的webrtc源码的编译路径相同。 解决方法&#xff1a;将python的安装路径和webrtc编译源码的路径放在同一个磁盘下。 错误2&#xff1a;Windows 默认不支持文件名或目录名长于260个字符的&#xff…

rvtptcontrol failed

rvtptcontrol failed RVT00-010:子例行程序 rvtooperation()返回错误。 原因&#xff1a;子例行程序 rvtooperation() 返回内部错误。返回的错误信息为&#xff1a; 措施&#xff1a; 请记下此错误编号以及无法读取例程 &routine 中配置文件选项 INV_DEBUG_TRACE 的值”这…

error C2338: /RTCc rejects conformant code错误解决

在编译一个项目时&#xff0c;发现在调试版本时提示这个出错&#xff1a; 1>------ 已启动生成: 项目: simulation2, 配置: Debug Win32 ------1>precompiled.cpp1>C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\yvals.h(112): error C2338: /RTC…

CSHOP后台设置SMTP发邮件提示 Error: need RCPT command 错误解决

其实错误原因并不是因为此错误&#xff0c;经检测&#xff0c;邮件服务器返回的真实错误是 501 mail from address must be same as authorization user 。只因为同时返回了 503 Error: need MAIL command 和 503 Error: need RCPT command &#xff0c;而ECSHOP只提示了最后一…

阿里云企业邮箱代理商:foxmal邮件发送RCPT错误怎么办?

阿里云企业邮箱代理商&#xff1a;foxmal邮件发送RCPT错误怎么办&#xff1f; 聚搜云是上海聚搜信息技术有限公司旗下品牌&#xff0c;坐落于魔都上海&#xff0c;服务于全球、2019年成为阿里云代理商生态合作伙伴。与阿里云代理商、腾讯云、西部数码、美橙互联、聚搜云,长期战…

foxmail发生RCPT错误

一&#xff0c; 问题 在前几天来到万达之后电脑要重新装系统&#xff0c;也没管别的就直接装了&#xff0c;然后电脑上所有的东西就没了&#xff0c;在装好之后要安装所需要的软件。安装之后就开始使用&#xff0c;就在使用foxmail的时候遇到问题了&#xff0c;也不知道发生了…

Windows下bat命令启动和关闭jar包

一、启动 这里需要将jar包和bat文件放在同一个目录下 启动命令代码如下 echo off start javaw -jar springboot.jar exit二、关闭 关闭命令代码如下 echo off set port8888 for /f "tokens1-5" %%i in (netstat -ano^|findstr ":%port%") do taskkil…

利用Bat命令批量修改文件名

因为科研需求&#xff0c;需要把文件名规范统一命名。 整体思路&#xff1a; 先获得原始文件名字&#xff08;带后缀&#xff09;&#xff0c;再导到excel里搞好新名字&#xff0c;构建好Bat的ren函数&#xff0c;完成修改。 具体措施&#xff1a; 1&#xff09;读取原本文件…