【算法】单调栈

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

目录

    • 单调栈的定义:
    • 伪代码:
    • 应用
      • 1.模板题
      • 2.视野总和问题
      • 3.柱状图中的最大矩形
      • 4.最大区间
    • 碎碎念:

单调栈的定义:

从名字上就能猜出来,这种数据结构在栈的基础上,栈内的元素是单调有序的,所以单调栈分为单调递增栈和单调递减栈(增减性的划分是根据栈顶到栈底的元素变化规律(搞不懂为什么定义要从栈顶开始看,栈是从栈底开始加入元素的呀,好像有一点反思维)

  • 单调递增栈: 从栈顶往栈底看,是单调递增的关系(含相等)
  • 单调递减栈: 从栈顶往栈底看,是单调递减的关系(含相等)

例子:现在有一组数:3,4,2,6,4,5,2,3,如果从左往右依次进单调递增栈,那么是这样操作的:在这里插入图片描述

伪代码:

以单调递增栈为例:

stack<int> st;
//此处一般需要给数组最后添加结束标志符
for (遍历这个数组)
{// 如果栈不为空且栈顶元素比压栈元素小,则栈顶元素出栈并作计算与更新结果,然后继续下一轮比较,直至该元素可以入栈;// 否则,(即栈为空,或栈顶元素大于等于压栈元素),直接入栈即可while ( !st.empty() && vec[st.top()] < vec[i] ) {st.pop();// Do something}st.push(i);  // 数组内的元素都要入栈,但入栈的是其下标
}

注意事项:

  • 为了保证栈内元素最后都能出栈,需要在数组结尾添加一个特殊的数字:
    对单调递增栈,在数组末尾添加一个最大数,如 INT_MAX; 对单调递减栈,在数组末尾添加一个最小数,如 INT_MIN.
  • 数组中的所有元素都要入栈一次和出栈一次,除了最后一个特殊元素;
  • 当一个元素出栈的时候,做计算,更新答案;
  • 当一个元素即将出栈时,记住整个栈内元素具有单调性,很多时候需要利用这个特性去做计算;
  • 一个元素可能会令栈中若干个元素出栈,但也可能是直接入栈

应用

1.模板题

洛谷P5788 【模板】单调栈

题目:
在这里插入图片描述
解题思路:
求每个数后第一个大于他的数。
直接看数字不太容易想,那么我们把题目转化一下:有 n n n 个人,每个人向右上方看,求她看到的第一个人。
在这里插入图片描述
比如第2个人,他的高度是4,那么他往右上方看,肯定看不到第3,第4个人,因为他们比他矮,被遮住了,那留着也没用,直接舍弃就好(我没有歧视矮,我也想长高QwQ)。那么大家是否看到了单调性?比他矮的人全部舍弃,留下的都是比他高的。所以我们可以用单调栈实现。由于是往右看,所以需要从后往前遍历数组。

代码:

#include <bits/stdc++.h>
#include <iostream>
#include <string.h>
#include <algorithm>
#define ll long long
#define qc ios::sync_with_stdio(false); cin.tie(0);cout.tie(0)
using namespace std;
const int MAXN = 3e6 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
int a[MAXN],ans[MAXN];
stack<int> s;//单调递增栈 int main()
{qc;int n;cin>>n;for(int i=1;i<=n;i++)cin>>a[i]; for(int i=n;i>0;i--){while(!s.empty() && a[s.top()]<=a[i])s.pop();ans[i]=s.empty()? 0:s.top();s.push(i);//压入下标}for(int i=1;i<n;i++)cout<<ans[i]<<" ";cout<<ans[n]<<endl;return 0;
}

2.视野总和问题

POJ-3250 Bad Hair Day

题目:
在这里插入图片描述
在这里插入图片描述
题目大意:
一群高度不完全相同的牛从左到右站成一排,每头牛只能看见它右边的比它矮的牛的发型,若遇到一头高度大于或等于它的牛,则无法继续看到这头牛和后面的其他牛的发型。给出这些牛的高度,要求每头牛可以看到的牛的数量的和。

解题思路1:
每个元素只会出栈一次,所以当他出栈的时候就可以计算出他可以看到的牛的个数了,即当前所在下标与自己下标的差值。

代码1:

#include <iostream>
#include <string.h>
#include <algorithm>
#include <stack>
#define ll long long
#define qc ios::sync_with_stdio(false); cin.tie(0);cout.tie(0)
using namespace std;
const int MAXN = 3e6 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
int a[MAXN];
stack<int> s;//单调栈 int main()
{
//	freopen("in.txt", "r", stdin);qc;int n;cin>>n;for(int i=1;i<=n;i++)cin>>a[i]; n++;a[n]=inf;ll ans=0;//千万记得开ll,不然就像我一样,debug一个小时,QwQfor(int i=1;i<=n;i++){while(!s.empty() && a[s.top()]<=a[i]){ans+=(i-s.top()-1);s.pop();}	s.push(i);}cout<<ans<<endl;return 0;
}

解题思路2:(一开始找不到第一个思路的bug时,看其他博客的思路找到的)
可以把问题等效成每头牛可以被看到的次数之和。
构建一个单调递增栈,栈底元素最大,从左到右依次读取牛的高度,当待入栈元素小于栈顶元素时,代表目前栈里的牛都可以看到这头牛,答案加上目前栈里的元素的个数,然后把该元素入栈,继续下一个比较;如果待入栈元素大于等于栈顶元素时,就要从栈中弹出比它小的,即删除不能看到这头牛的元素,答案加上目前栈里所剩元素的个数,然后把该元素入栈,继续下一个比较。注意!当你计算栈中元素个数时,要记得减去特殊元素哦!!!

代码2:

#include <iostream>
#include <string.h>
#include <algorithm>
#include <stack>
#define ll long long
#define qc ios::sync_with_stdio(false); cin.tie(0);cout.tie(0)
using namespace std;
const int MAXN = 80000 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
int a[MAXN];
stack<int> s;//单调栈 int main()
{
//	freopen("in.txt", "r", stdin);qc;int n;cin>>n;for(int i=1;i<=n;i++)cin>>a[i]; s.push(inf);//压入特殊元素 ll ans=0;for(int i=1;i<=n;i++){while(!s.empty() && s.top()<=a[i]){s.pop();}ans+=(s.size()-1);//有一个特殊元素要减去 s.push(a[i]);}cout<<ans<<endl;return 0;
}

3.柱状图中的最大矩形

Acwing 131. 直方图中最大的矩形

题目:
在这里插入图片描述
在这里插入图片描述
解题思路:
维护一个单调递减栈,即从栈底到栈顶依次增大,这样栈中的元素往右扩展,一定可以成为更大的矩形。如题目中的图所示,当该元素要被弹出栈的时候,那么就可以计算以该元素为高往右最大扩展的矩形的面积了。计算出面积后,一直更新最大值,答案就出来了。

代码:

#include <bits/stdc++.h>
#include <iostream>
#include <string.h>
#include <algorithm>
#define ll long long
#define qc ios::sync_with_stdio(false); cin.tie(0);cout.tie(0)
using namespace std;
const int MAXN = 2e5 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
vector<int> heights;ll largestRectangleArea() 
{heights.push_back(INT_MIN);//特殊元素 ll result = 0;stack<int> st;ll h=0, w=0;int top_id = 0;for (int i=0; i<heights.size(); ++i){// 单调栈内元素是递增的 while ( !st.empty() && heights[st.top()] > heights[i] ) {top_id = st.top();st.pop();h = heights[top_id];if (st.empty()) //栈为空,代表是目前最小的,所以0~i-1所有都可以满足 w = i;else w = i - st.top() - 1;//i:将要入栈的下标,st.top():下一个可能出栈的元素小标 result = max(result, w*h);}st.push(i);}return result;
}int main()
{
//	freopen("in.txt", "r", stdin);qc;int n,tmp;while(cin>>n){if(n==0)break;while(!heights.empty())//清空vector heights.pop_back();for(int i=0;i<n;i++){cin>>tmp;heights.push_back(tmp);}ll ans=largestRectangleArea();cout<<ans<<endl;} return 0;
}

题目变形:

洛谷 P4147 玉蟾宫

解题思路:

  • 按行去划分,O(n)枚举每一行,对该行即以上的部分做最大子矩阵处理;
  • 用pos数组记录每行向上可延伸的最大距离,预处理的方式为:
    (1)读到一个‘F’,该处pos=上一行该列pos的值+1;
    (2)读到一个‘R’,该处pos=0(因为该处不可向上伸展);

代码:

#include <bits/stdc++.h>
#include <iostream>
#include <string.h>
#include <algorithm>
#define ll long long
#define qc ios::sync_with_stdio(false); cin.tie(0);cout.tie(0)
using namespace std;
const int MAXN = 2e5 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
int n,m,pos[1010][1010],maxs=0;
ll ans;
vector<int> heights;ll largestRectangleArea(int k) 
{while(!heights.empty())heights.pop_back();for(int i=1;i<=m;i++)heights.push_back(pos[k][i]);heights.push_back(INT_MIN);//特殊元素 ll result = 0;stack<int> st;ll h=0, w=0;int top_id = 0;for (int i=0; i<heights.size(); ++i){// 单调栈内元素是递增的 while ( !st.empty() && heights[st.top()] > heights[i] ) {top_id = st.top();st.pop();h = heights[top_id];if (st.empty()) //栈为空,代表是目前最小的,所以0~i-1所有都可以满足 w = i;else w = i - st.top() - 1;//i:将要入栈的下标,st.top():下一个可能出栈的元素小标 result = max(result, w*h);}st.push(i);}return result;
}int main()
{
//	freopen("in.txt", "r", stdin);qc;char c;cin>>n>>m;for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){cin>>c;if(c=='F')pos[i][j]=pos[i-1][j]+1;}}ll tmp=0;for(int i=1;i<=n;i++){tmp=largestRectangleArea(i);ans=max(tmp,ans);}cout<<3*ans<<endl;return 0;
}

4.最大区间

POJ 2796 Feel Good

题目:
在这里插入图片描述
在这里插入图片描述

题目大意:

给你一组数字,求一区间,使得区间元素和乘以区间最小值最大,结果要求输出这个最大值和区间的左右端点

解题思路:
首先应该能想到暴力的做法。想要求出答案,那么对于每一个以 a i a_i ai 为最小的元素的区间,它的左右端点都是比它小的数(为了方便,用开区间来描述)。所以只要以 a i a_i ai 的起点向左右扩展就行了,这样的复杂度是 O ( n 2 ) O(n^2) O(n2)
那么如何来优化呢?计算区间的和用前缀和就行了,那么区间的端点呢?可以用单调栈来解决。我们用单调栈来维护区间最小值,那么对当前元素来说,在它之前的一个最小值和在它之后的一个最小值就是区间的两个端点。所以我们用一个单调递增栈从前到后面扫一遍,就求出 a i a_i ai 左边比 a i a_i ai 小的一个元素,同样倒着扫一遍就能求出另一边的元素。

代码:

//#include <bits/stdc++.h>
#include <iostream>
#include <string.h>
#include <algorithm>
#include <stack>
#define ll long long
#define qc ios::sync_with_stdio(false); cin.tie(0);cout.tie(0)
using namespace std;
const int MAXN = 1e5 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int N=1e5+100;
const int maxn = 1e5+10;
ll arr[MAXN], pre[MAXN];
int n, l[MAXN], r[MAXN];
stack<int> st;
int main() {scanf("%d", &n);for (int i = 1; i<=n; ++i) {scanf("%lld", &arr[i]);pre[i] += pre[i-1]+arr[i];}for (int i = 1; i<=n; ++i) {while(!st.empty() && arr[st.top()]>=arr[i]) st.pop();l[i] = st.empty() ? 1 : st.top()+1;st.push(i);}while(!st.empty()) st.pop();for (int i = n; i>=1; --i) {while(!st.empty() && arr[st.top()]>=arr[i]) st.pop();r[i] = st.empty() ? n : st.top()-1;st.push(i);}ll maxx = -1; int pl,pr;for (int i = 1; i<=n; ++i)if (maxx < (ll)arr[i]*(pre[r[i]]-pre[l[i]-1])) {maxx = (ll)arr[i]*(pre[r[i]]-pre[l[i]-1]);pl=l[i];pr=r[i];}cout << maxx << "\n" << pl << " " << pr << endl; return 0;
}

碎碎念:

这篇算法总结花了我好大的心血啊…一道一道到原题,还有因为没开long long找了1小时的bug…终于完工了!睡觉睡觉,大家晚安呐!


http://chatgpt.dhexx.cn/article/5TNjghPn.shtml

相关文章

单调栈详解

定义&#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…

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…