第九章:单调栈与单调队列

article/2025/9/30 22:06:52

单调栈与单调队列

  • 一、单调栈
    • 1、什么是单调栈?
    • 2、单调栈的模板
      • (1)问题:
      • (2)分析:
  • 二、单调队列
    • 1、什么是单调队列
    • 2、单调队列模板
      • (1)问题
      • (2)分析

一、单调栈

1、什么是单调栈?

单调栈顾名思义就是栈中的元素是具有单调性的,比如依次增大的数则具有单调递增的性质,依次减小的数则具备单调递减的性质。

2、单调栈的模板

(1)问题:

在这里插入图片描述

(2)分析:

我们可以写出一个暴力做法:如下:

#include<iostream>
using namespace std;
const int N=100010;
int a[N];
int main()
{int n;cin>>n;for(int i=0;i<n;i++){scanf("%d",a+i);}for(int i=0;i<n;i++){int j,flag=0;for(j=i-1;j>=0;j--){if(a[j]<a[i]){flag=1;break;}}if(flag)cout<<a[j]<<" ";else cout<<-1<<" ";}return 0;
}

但是这种暴力做法的时间复杂度是O(N2),因此当我们提交的时候会发生超时的情况。因此,我们需要寻找另外的算法来优化时间复杂度。

此时,我们可以采用空间换时间的思想,具体思路如下:

我们除了创建一个数组去存储输入的数据外,我们再开辟一块空间来处理数据。题目中提到的是左端的第一个,此时我们很容易就联想到了栈这种数据结构,因为这种数据结构是先进后出,假设我们再存储到数组中的同时,也存储到栈中,那么我们的栈顶元素一定是前距离a[i]左端最近的数字。

因此,当我们使用栈的时候,就能够保证我们输出的栈顶元素是距离目标元素最近的,那么我们接下来的问题就是如何保证,我们输出的栈顶元素一定是比目标元素小呢?

我们看下面的思路:

  • step1 : 在栈不为空的条件下,只要栈顶元素大于等于栈顶元素,那么我们就删除掉栈顶元素。
  • step2 : 让目标元素进栈。

然后我们来分析一下上面的思路:
步骤一就保证了栈中的任意一个元素,一定是小于后一个元素的。所以此时栈中的元素呈现了单调递增的特性。

接着我相信大家会在两个点上感到疑惑:

1、删除的元素会不会是后续过程中的答案?

我们看下面的这种情况下,5是否有可能成为后续过程的答案?
假设我们的目标元素是6,那么5小于6,但是由于5>3,根据不等式的传递性,3也小于6,但是3距离元素6更近,所以3一定是答案。那么这是数字3存在的情况。我们假设数字3被删除了,我们假设3遇到了2,此时3大于等于2,所以3会被删除,但是此时入栈的元素是不是2,很明显再2的存在下,5更不可能成为答案。综上所述,我们删除的元素是不可能成为答案的。
在这里插入图片描述
2、目标元素一定要入栈吗?

答案是一定的,因为通过步骤一的循环处理,栈具备单调递减的特点,因此我们的目标元素就会成为栈中最小的元素,同时这个元素还是距离下一个元素最近的。假设这个元素都大于目标元素,那么剩余元素必定大于目标元素,所以这个栈中就不会再存在答案了。综上所述,我们的目标元素是最有可能成为下一次判断时的答案。

因此,我们的思路一定能够输出正确的答案,那么我们如何实现呢?

#include<iostream>
using namespace std;
const int N=100010;
int a[N],stk[N],top;
int main()
{int n;scanf("%d",&n);for(int i=0;i<n;i++){scanf("%d",a+i);while(stk&&stk[top]>=a[i])top--;if(!top)cout<<-1<<" ";else cout<<stk[top]<<" ";stk[++top]=a[i];}return 0;
}

我们发现上面的时间复杂度是O(N),大大的优化了时间。

二、单调队列

1、什么是单调队列

单调队列就是队列中的元素具有单调性的队列。

2、单调队列模板

(1)问题

在这里插入图片描述

(2)分析

我们以最小值为例:

这道题我们同样可以采用暴力的做法:两个for循环相互嵌套。我们发现这种情况的时间复杂度是O(N2)。很明显,这种算法是非常低效的。

那么我们换一种算法:

我们先来思考一下,这道题适合什么样的数据结构?不难发现,这个滑动窗口是从前向后移动,即先进先出。所以我们想到了队列这种数据结构来存储。

那么队列是在队头输出的,因此我们需要保证队头输出的数据是最小值。此时受到刚才单调栈的影响,我们很容易想到这里应该是构造一个单调递增的队列,这样我们就能够保证队列中的第一个元素是最小的。

此时大家就会有一个疑问,为什么必须单调呢?我们明明只需要保证第一个最小即可。那么我们假设第一个是最小的,第二个是最大的。当我们的第一个元素滑出窗口的时候,第二个元素成为了第一个元素,此时第一个元素就不再是最小的。因此,我们保证单调性的时候,就能够保证每个元素做队头的时候,都小于后续的元素。

我们如何实现呢?单调递增的特点就是前一个元素小于该元素。接着我们就能写出下列的步骤:

  • step1 : 在队列不为空的条件下,判断第一个元素是否在窗口内。
  • step2 : 若队尾元素大于等于目标元素则删除
  • step3 :目标元素进队。

在进行步骤1的判断的时候,我们需要一个计数器去记录,但是这样是相当麻烦的,因此我们将下标存储到队列中,这样的话,我们就能够通过下标的方式去判断队头是否出队。

我们还是解决一下几个问题:
为什么要不满足单调性的元素可以删除?
在这里插入图片描述

在这个队列中,只要有3的存在,4必不可能是答案,那么当3不存在的时候呢?那么4更不可能是答案了,因为我们是在队头删除元素的。所以3出队的前提是4出队。因此只有4没有3的情况是不存在的。所以我们可以删除。

那么为什么目标元素一定要进队呢?
因为经过单调性的处理,目标元素就成了队中最大的,虽然目标元素队前的元素比其要小,但是他们都是先出队的,所以当前面的元素都出队的时候,该目标元素有可能成为答案,所以我们要让每个目标元素的下标都进队。

#include<iostream>
using namespace std;
const int N=1000010;
int a[N],q[N],h,t;
int main()
{int n,k;cin>>n>>k;for(int i=0;i<n;i++){scanf("%d",a+i);if(h<=t&&q[h]<i-k+1)h++;while(h<=t&&a[q[t]]>=a[i])t--;q[++t]=i;if(i-k+1>=0)printf("%d ",a[q[h]]);}puts("");h=0,t=-1;for(int i=0;i<n;i++){if(h<=t&&q[h]<i-k+1)h++;while(h<=t&&a[q[t]]<=a[i])t--;q[++t]=i;if(i-k+1>=0)printf("%d ",a[q[h]]);}return 0;
}

我们发现,判断单调性的时候,我们是在队尾删除,判断是否在窗口内的时候,是在队头删除的。所以这不是普通的队列,而是双端队列,即STL中的deque。所以我们可以通过deque容器来实现。

#include<iostream>
#include<deque>
#include<vector>
using namespace std;
int main()
{int n,k,b;cin>>n>>k;vector<int>a;deque<int>q;for(int i=0;i<n;i++){scanf("%d",&b);a.push_back(b);if(!q.empty()&&q.front()<i-k+1)q.pop_front();while(!q.empty()&&a[q.back()]>=a[i])q.pop_back();q.push_back(i);if(i-k+1>=0)printf("%d ",a[q.front()]);}puts("");q.clear();for(int i=0;i<n;i++){if(!q.empty()&&q.front()<i-k+1)q.pop_front();while(!q.empty()&&a[q.back()]<=a[i])q.pop_back();q.push_back(i);if(i-k+1>=0)printf("%d ",a[q.front()]);}return 0;
}

http://chatgpt.dhexx.cn/article/7cPAEsZI.shtml

相关文章

单调栈算法详解

单调栈算法详解 单调栈使用模板 stack<int> st; //此处一般需要给数组最后添加结束标志符&#xff0c;具体下面例题会有详细讲解 for (遍历这个数组){if (栈空 || 栈顶元素大于等于当前比较元素){入栈;}else{while (栈不为空 && 栈顶元素小于当前元素){栈顶元素…

单调队列和单调栈详解

这里是我的blog&#xff1a;有更多算法分享。排版可能也会更好看一点v https://endlesslethe.com/monotone-queue-and-stack-tutorial.html 前言 单调栈和单调队列算是栈和队列的高级应用吧&#xff0c;在公司面试中应该是不怎么会出现的&#xff08;除非算法岗&#xff1f;…

什么是单调栈

什么是单调栈 单调栈就是单调递增或者单调递减的栈&#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…