单调队列和单调栈详解

article/2025/9/30 22:02:13


这里是我的blog:有更多算法分享。排版可能也会更好看一点=v=
https://endlesslethe.com/monotone-queue-and-stack-tutorial.html

前言

单调栈和单调队列算是栈和队列的高级应用吧,在公司面试中应该是不怎么会出现的(除非算法岗?)。
因为原理比较简单,网络上的相关资料反而对于这两个东西说得都不甚清楚,尤其是它们的应用方法。最基本的两本中文算法书“紫书”和“白皮”都没有提到。

而我因为平日要做的事情也很多,仓促中写下的这篇文章难免表达上会有不清晰的地方和各种疏漏,希望读者不吝赐教=v=。

栈和队列这两种基础数据结构戳:TBC。

先说明一下,无论是栈还是队列,我们把元素进入的一端称作“尾部”,并用双引号标出,另一端称作“尾部”。对于栈,头部即是栈底,尾部即是栈顶。对于队列,头部对应队首,尾部对应队尾。

monotone queue and stack

注:我不确定单调队列是否翻译成monotone queue,算法导论上没有提到这个数据结构,Bing上也没有搜索到对应它的结果(倒是返回了关于优先队列的结果)。

单调栈

我们都已经非常熟悉栈了,它具有先入后出的性质。而单调栈为了满足单调的要求,增加了一个性质:

  • 从栈顶到栈底的元素是严格递增(or递减)

显然,这和我们的正常的流程有一定矛盾之处——如果栈中是5 4 3 2 1,如果压入3怎么办?
原来我们只需要添加到栈尾即可,现在则需要将3 2 1弹出,再压入3,栈变成5 4 3
注:弹出的元素我们直接舍弃掉。

具体进栈过程

  • 对于单调递增栈,若当前进栈元素为e,从栈顶开始遍历元素,把小于e或者等于e的元素弹出栈,直接遇到一个大于e的元素或者栈为空为止,然后再把e压入栈中。
  • 对于单调递减栈,则每次弹出的是大于e或者等于e的元素。

一个单调递增栈的例子:

进栈元素分别为3,4,2,6,4,5,2,3

第i步操作结果
13进栈3
23出栈,4进栈4
32进栈4 2
42、4出栈,6进栈6
54进栈6 4
64出栈,5进栈6 5
72进栈6 5 2
82出栈,3进栈6 5 3

这里提供一个递增的单调栈的图,元素依次为3,2,8,4,5,7,6,4
注意看随着i的变化,栈中元素的变化。
monotone queue example 1

单调队列

对于单调队列,从单调栈的性质我们可以类推出:

  • 从队列头到队列尾的元素是严格递增

添加元素e到队尾时,我们采取的解决方法同样是,先从队尾删去小于等于e的元素。
注意,普通的队列queue是不支持从队尾删除的,我们需要使用双端队列deque,即有两个指针,一头一尾。

队列的大小问题

在谈及单调栈时,我略去了栈的大小这一个问题,因为在实际使用中(比如函数调用栈)栈就通常没有大小的概念。而对于队列,它的大小就很重要了。
如果队列满了,我们的解决方法是,将队列头的元素弹出,再添加新的元素到队列尾。

具体入队过程

  • 对于单调递增队列,设当前准备入队的元素为e,从队尾开始把队列中的元素逐个与e对比,把比e大或者与e相等的元素逐个删除,直到遇到一个比e小的元素或者队列为空为止,然后把当前元素e插入到队尾。
  • 对于单调递减队列也是同样道理,只不过从队尾删除的是比e小或者与e相等的元素。

一个递增单调队列的例子

队列大小不能超过3,入队元素依次为3,2,8,4,5,7,6,4

第i步操作结果
13入队3
23从队尾出队,2入队2
38入队2 8
48从队尾出队,4入队2 4
55入队2 4 5
62从队头出队,7入队4 5 7
77从队尾出队,6入队4 5 6
86、5、4从队尾出队,4入队4

monotone queue example 2

单调队列和单调栈的区别和联系

单调队列和单调栈的相同点

  • 单调队列和单调栈的“头部”都是最先添加的元素,“尾部”都是最后添加的元素。
  • 递增和递减的判断依据是:从栈底(队尾)到栈顶(队首),元素大小的变化情况。所以队列和栈是相反的。
  • 它们的操作是非常相似的。当队列长度为无穷大时,递增的单调队列和递减的单调栈,排列是一样的!
    原因在于,长度为无穷大的的队列不会在“头部”有popfront操作,而在“尾部”的操作是一模一样的:数据都从“尾部”进入,并按照相同的规则进行比较。
  • 两者维护的时间复杂度都是O(n),因为每个元素都只操作一次。

区别

  • 队列可以从队列头弹出元素,可以方便地根据入队的时间顺序(访问的顺序)删除元素。
  • 这样导致了单调队列和单调栈维护的区间不同。当访问到第i个元素时,单调栈维护的区间为[0, i),而单调队列维护的区间为(lastpop, i)
  • 单调队列可以访问“头部”和“尾部”,而单调栈只能访问栈顶(也就是“尾部”)。这导致单调栈无法获取[0, i)的区间最大值/最小值。

综上所述,单调队列实际上是单调栈的的升级版。单调栈只支持访问尾部,而单调队列两端都可以。当然,单调栈的编程上(两个函数)比单调队列(三个函数)要简单。

单调队列和单调栈的性质

下面的总结,如果没有特别指出是单调队列/单调栈,那么就不区分队列和栈,而且从“头部”到“尾部”数据是严格递减的,请读者自行注意。
  1. 具有单调性
  2. 容器中的元素个数永远不为空。(因为当添加一个元素时,它要么直接被添加到“尾部”,要么弹出k个比它小的数后再被添加到“尾部”)
  3. 对于一个元素i,我们可以知道在它左边区间,第一个比它小的值,也就是\({\rm{Max({ v[x]|x < i \& \& v[x] < v[i]} )}}\)
    在元素添加的过程中,我们会不断弹出比它小的值,最后一个弹出的值,即为所求。如果没有元素被弹出,那就无法求出,虽然这个数一定存在。
    顺便在这里多提一句,第二个比它小的数是一定不知道的,因为不确定是否被弹出
  4. 对于一个元素i,我们可以知道在它左边区间,第一个比它大的值,也就是\(Min\left( {{ v\left[ x \right]|x < i\& \& v\left[ x \right] > v\left[ i \right]} } \right)\)
    在弹出比i小的所有元素后,栈顶的元素即为所求。如果栈为空,也无法求出。
  5. 根据2和3,它们是元素插入时所获得的信息,我们可以推出当元素被弹出时能获得的信息:在右边区间,第一个比它大的值。
  6. 我们可以统计在添加元素过程中,弹出了多少个元素。

注:这里的大于和小于并不严谨,是为了表述尽量简单。请读者自己注意大于/大于等于,小于/小于等于。根据原则:容器中等于e的元素也会被弹出,进行判断即可。

单调队列和单调栈的应用

单调队列

  • 可以查询区间最值(不能维护区间k大,因为队列中很有可能没有k个元素)
  • 优化DP(见参考文献3)
    单调队列一般是用于优化动态规划方面问题的一种特殊数据结构,且多数情况是与定长连续子区间问题相关联。

单调栈即可完成的

对于某个元素i:

  • 左边区间第一个比它小的数,第一个比它大的数
  • 确定这个元素是否是区间最值
  • 右边区间第一个大于它的值
  • 到 右边区间第一个大于它的值 的距离
  • 确定以该元素为最值的最长区间

在具体题目里,如何使用单调栈和单调队列是一目了然的,不要强迫自己记忆,而是要理解
要想掌握好单调栈和单调队列,必须要做一些题

具体代码

单调队列

//在“尾部”添加元素x
while (l != r && mq[r] <= x) r--;
mq[++r] = x;//查询队首元素
if (l != r) printf("%d\n", mq[l+1]);
else printf("-1\n");//弹出队首元素
if (l != r) l++;

单调栈

//在“尾部”添加元素
while (r != 0 || ms[r] <= x) r--;
ms[++r] = x;//查询栈顶元素
if (r != 0) printf("%d\n", ms[r]);
else printf("-1");

注:在前面的情况,我们是直接压入了元素e。但我们往往选择压入元素e的index(依据题型来决定),需要稍作修改,这里不提供对应代码。

题目总结

  • FZU 1894
    1.C name rp 名字为name人品为rp的人排到队尾
    2.G 队首的人出队
    3.Q 询问当前队列中rp最高的
    实现这三个操作。模板题。
    AC代码
  • POJ 2796
    最大化(区间min * 区间和)
    使用单调栈,从某一元素i向左右区间延伸,找到最大区间。
    AC代码
  • HDU 5033
    模板题
  • FJSDFZOJ 1308 音乐会的等待/诺诺的队列
    对于l和r,如果[l, r]内没有大于min(l,r)的数,则这两个数可以互相看见。
    求能互相看见的对数
    标程对题意的理解比较奇怪。
    AC代码

参考文献

I. 单调队列 单调栈总结
II. 单调栈的介绍以及一些基本性质
III. 单调队列,单调栈总结
IV. 单调队列或单调栈的学习及认识


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

相关文章

什么是单调栈

什么是单调栈 单调栈就是单调递增或者单调递减的栈&#xff0c;也就是栈底到栈顶递增或递减&#xff0c;根据单调栈的的这种结构&#xff0c;可以很容易想到运用单调栈可以很容易的把O(n)的时间复杂度优化到O(n),如果使用数组的话&#xff0c;相对的空间复杂度也不会太高 示例 …

Java实现之单调栈

目录 一.单调栈 二.每日温度 1.题目描述 2.问题分析 3.代码实现 三.下一个更大元素 I 1.题目描述 2.问题分析 3.代码实现 四.下一个更大元素 II 1.题目描述 2.问题分析 3.代码实现 一.单调栈 通常是一维数组&#xff0c;要寻找任一个元素的右边或者左边第一个比自…

[数据结构]单调栈

单调栈 这是笔者的第一篇博客&#xff0c;由于笔者自身水平的限制。用词可能不够准确&#xff0c;句子不太通顺&#xff0c;代码水平可能也不太行&#xff0c;敬请指出&#xff0c;感激不尽&#xff01; 我们都知道栈&#xff08;Stack&#xff09;是一种先入后出的数据结构&am…

单调栈和单调队列

本文摘自博客&#xff0c;欢迎前往博客以获得更好的体验。 单调栈 从名字上就听的出来&#xff0c;单调栈中存放的数据应该是严格单调有序的&#xff0c;具有以下两个性质。 满足从栈顶到栈底的元素具有严格的单调递增或单调递减性&#xff1b;满足栈的后进先出特性&#xff…

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

目录 1.单调栈的基本概念 &#xff1a; 2.单调栈的应用 2.1单调栈 2.2单调栈进阶 2.3最大矩形面积 2.4最大矩形 2.5统计全为1的子矩阵数量 ​ 1.单调栈的基本概念 &#xff1a; 相信大家对栈都非常的熟悉&#xff1f;栈有一个非常鲜明的特点&#xff1a;先进后出 而所谓 单调栈…

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", …