单调栈和单调队列

article/2025/9/30 22:04:35

本文摘自博客,欢迎前往博客以获得更好的体验。

单调栈

从名字上就听的出来,单调栈中存放的数据应该是严格单调有序的,具有以下两个性质。

  1. 满足从栈顶到栈底的元素具有严格的单调递增或单调递减性;
  2. 满足栈的后进先出特性,即越靠近栈底的元素越早进栈。

单调栈也分为单调递增栈和单调递减栈。

  • 单调递增栈:单调递增栈就是从栈底到栈顶数据是从小到大
  • 单调递减栈:单调递减栈就是从栈底到栈顶数据是从大到小

伪代码

stack<int> st;
for (遍历这个数组){if (栈空 || 栈顶元素大于等于当前比较元素){入栈;}else{while (栈不为空 && 栈顶元素小于当前元素){栈顶元素出栈;更新结果;}当前数据入栈;}
}

单调栈的维护

如何维护一个单调栈?出栈很简单,与普通栈相同,关键操作是入栈

具体进栈过程

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

对于一个元素a,如果栈空则直接入栈。否则比较a与栈顶元素。假设一个单调递增的栈,若a > 栈顶元素,则直接把a入栈,栈仍满足单调的性质。若a < 栈顶元素,则将栈顶元素出栈,直到满足a < 栈顶元素,此时将a入栈。

例子

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

例题

同样,C++在实现时,我们也可以使用STL中的栈。

柱状图中最大的矩形

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

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

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

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

思路:

  • 初看此题,第一个想到的就是暴力遍历。而暴力遍历又分两种情况,即 枚举宽 与 枚举高。
  • 枚举宽时,我们使用两重循环枚举矩形的左右边界以固定宽度 w ,时间复杂度为 O ( N 2 ) O(N^2) O(N2)
  • 枚举高时,我们可以使用一重循环枚举某一根柱子,将其固定为矩形的高度 h。随后我们从这跟柱子开始向两侧延伸,直到遇到高度小于 h 的柱子,就确定了矩形的左右边界。时间复杂度也为 O ( N 2 ) O(N^2) O(N2)
  • 很容易发现,枚举宽的做法已经使用了双重循环,很难再优化。所以我们通过对枚举高这一做法进行优化。

那么,该如何去寻找左右边界值呢?

这时,我们便可以使用单调栈进行优化。

在思路中,我们枚举高说向左右遍历寻找。未来降低时间复杂度,我们可以使用单调栈只向一边遍历。

当第i个柱子进栈时,如果栈顶的高度低于或等于第i个柱子,则第i个柱子进栈;

如果栈顶的高度高于第i个柱子,则出栈,并计算以柱子A为高的矩形最大面积。

同时,为防止越界问题,我们可以在左右两侧虚拟两根无限低的柱子,记高度为 0 0 0

C

int largestRectangleArea(int* heights, int heightsSize){int top = -1;int area, i;int maxarea = 0;int *stack = (int *)malloc(sizeof(int) * (heightsSize + 2));int *buff = (int *)malloc(sizeof(int) * (heightsSize + 2));buff[0] = 0;for (int i = 1; i <= heightsSize; i++) {buff[i] = heights[i - 1];}buff[heightsSize + 1] = 0;stack[++top] = 0;for (i = 1; i < heightsSize + 2; i++) {while (top > 0 && buff[i] < buff[stack[top]]) {area = (i - stack[top - 1] - 1) * buff[stack[top]];maxarea = maxarea > area ? maxarea : area;top--;}stack[++top] = i;}return maxarea;
}

C++

int largestRectangleArea(vector<int>& heights) {int n = heights.size();vector<int> left(n), right(n, n);stack<int> mono_stack;for (int i = 0; i < n; ++i) {while (!mono_stack.empty() && heights[mono_stack.top()] >= heights[i]) {right[mono_stack.top()] = i;mono_stack.pop();}left[i] = (mono_stack.empty() ? -1 : mono_stack.top());mono_stack.push(i);}int ans = 0;for (int i = 0; i < n; ++i) {ans = ans > ((right[i] - left[i] - 1) * heights[i]) ? ans : ((right[i] - left[i] - 1) * heights[i]);}return ans;
}

单调队列

队列中元素之间的关系具有严格单调有序性,而且,队首和队尾都可以进行出队操作,只有队尾可以进行入队操作。因此,在单调队列中求最值十分容易。

单调队列与普通队列有些不同,因为右端既可以插入又可以删除,因此在代码中通常用一份数组和 r e a r rear rear r e a r rear rear 两个指针来实现,而不是使用 STL 中的 q u e u e queue queue。如果一定要使用 STL ,那么则可以使用双端队列(即两端都可以插入和删除),即 d e q u e deque deque

队列的大小问题

在谈及单调栈时,无需关心栈的大小。而对于队列,队列的大小就很重要了。如果队列满了,我们的解决方法是,将队列头的元素弹出,再添加新的元素到队列尾。

单调队列的维护

具体入队过程

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

例子

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

例题

滑动窗口

给定一个数列,从左至右输出每个长度为 m m m的数列段内的最小数和最大数。

Window positionMinimum valueMaximum value
[1 3 -1] -3 5 3 6 7-13
1 [3 -1 -3] 5 3 6 7-33
1 3 [-1 -3 5] 3 6 7-35
1 3 -1 [-3 5 3] 6 7-35
1 3 -1 -3 [5 3 6] 736
1 3 -1 -3 5 [3 6 7]37

数列长度: N < = 1 0 6 N <= 10^6 N<=106 m < = N m<=N m<=N

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 1e6+5;
int n, k, a[maxn];
int q[maxn];//队列,存的是下标
int main(){scanf("%d%d",&n,&k);for(int i = 1; i <= n; ++i) scanf("%d",&a[i]);//维护最小值,序列要单调递增int front = 1, rear = 0;for(int i = 1; i <= n; ++i){//如果如果尾部的在滑块之外,则删掉他们while(front <= rear && q[front]+k <= i) ++front;//如果头部的比当前的大,那么a[i]从头进去,队列就不递增了,所以删掉比a[i]大的头部while(front <= rear && a[i] < a[q[rear]]) --rear;q[++rear] = i;//头部插入iif(i >= k) printf("%d ", a[q[front]]);//因为单调递增,所以答案在a的下标就是q[front]}//维护最大值,同上putchar('\n');memset(q,0,sizeof(q));front = 1, rear = 0;for(int i = 1; i <= n; ++i){while(front <= rear && q[front]+k <= i) ++front;while(front <= rear && a[i] > a[q[rear]]) --rear;q[++rear] = i;if(i >= k) printf("%d ", a[q[front]]);}return 0;
}

单调栈与单调队列

应用

单调队列

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

单调栈

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

相同点

  1. 单调队列和单调栈的“头部”都是最先添加的元素,“尾部”都是最后添加的元素。
  2. 递增和递减的判断依据相同:从栈底(队尾)到栈顶(队首),元素大小的变化情况。所以队列和栈是相反的。
  3. 两者维护的时间复杂度都是 O ( n ) O(n) O(n),每个元素都只操作一次。

不同点

  1. 单调队列可以从队列头弹出元素,可以方便地根据入队的时间顺序(访问的顺序)删除元素。
  2. 单调队列和单调栈维护的区间不同。当访问到第i个元素时,单调栈维护的区间为 [ 0 , i ) [0, i) [0,i),而单调队列维护的区间为 ( l a s t p o p , i ) (lastpop, i) (lastpop,i)
  3. 单调栈无法获取 [ 0 , i ) [0, i) [0,i)的区间最大值/最小值。因为单调队列可以访问“头部”和“尾部”,而单调栈只能访问栈顶(也就是“尾部”)。

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

相关文章

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

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

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 前言 解决…