C++ 线性数据结构系列之低调而强大的单调栈

news2025/4/28 10:36:31

1. 前言

单调栈是在栈基础上进行变化后的数据结构。除了遵循栈的先进后出的存储理念,在存储过程中还需保持栈中数据的有序性。

根据栈中数据排序的不同,单调栈分为:

  • 单调递增栈:从栈顶部向栈的底部,数据呈递增排序。
  • 单调递减栈:从栈顶部向栈的底部,数据呈递减排序。

现有数列[3,6,1,8,5],如使用单调递增栈存储时,其输入、输出流程如下:

  • 初始栈为空,数据3直接入栈。

1.png

  • 因原始数列中的数值6大于栈顶数据3,直接入栈后,无法保持栈的递增性。则需要先把栈顶数据3删除后,再入栈。

2.png

  • 原始数列中的数据1小于栈顶数据6,因入栈后可以保持栈中数据的递增性。数据1直接入栈。

3.png

  • 原始数列中的数据8大于栈顶数据1,则需删除栈顶数据1。数据8任然大于栈顶数据6,则继续删除栈顶数据6后再入栈。

4.png

  • 数据5比栈顶数据8小,可直接入栈。

5.png

  • 此时可发现,栈中还有2 个数据。而单调栈要求每一个数据都必须入栈、出栈一次。为了保证栈中所有数据能出栈。可以在原数列的最后面加上一个比原始数列中所有值都大的一个数据充当结束标志性数据。

6.png

编码实现单调递增栈存储过程:

#include <iostream>
//内置栈容器
#include <stack>
using namespace std;
int main() {
	//INT_MAX :表示 int 最大值
	int nums[6]= {3,6,1,8,5,INT_MAX};
	stack<int> st;
	//递增方式添加数据
	for(int pos=0; pos<6; pos++ ) {
		while(!st.empty() && nums[st.top()]<=nums[pos] ) {
			//输出
			cout<<nums[ st.top()  ]<<"\t";
			//删除
			st.pop();
		}
		//入栈
		st.push(pos);
	}
	cout<<"\n栈中的内容:"<<st.top()<<endl;
	return 0;
}

使用单调栈时,需要注意以下几点:

  • 对单调递增栈,在数组末尾添加一个最大数,如 INT_MAX
  • 对单调递减栈,在数组末尾添加一个最小数,如 INT_MIN
  • 数组中的所有元素都要入栈、出栈一次,除了最后一个特殊元素。

单调栈可以用于求解某个数字左边或右边第一个比之大或比之小的第一个数字。

2. 单调栈的应用

2.1 下一个较大数字

题目描述:

给定数组[1,4,2,3,5]求每一个数字后面第一个比之大的数字。

解题思路:

可以使用单调递增栈对原始数列进入输入输出。其实现过程如下图所示:

  • 初始时,准备好一个空栈、一个一维数组,用来存储原始数列中每一个数字后面第一个比之大的数字。元素1入栈。

7.png

  • 抽出原始数列中的数值4,因此数据比栈顶元素大,而又因需要维护栈的递增性。故,栈顶元素1需要出栈,且说明元素1遇到到第一个比之大的数据。

8.png

  • 抽出原始数列中的数值2,因此数据比栈顶元素小,可以直接入栈。

9.png

  • 抽出原始数列中的数值3,因此数比栈顶元素2大,故,栈顶元素2出栈且说明入栈的数据是它遇到的第一个比之大的数字。

    至此,应该能得到一个结论:为了维护栈的单调递增性。

    如果一个数据能直接入栈,则说明它比栈顶的元素小。

    **如果一个数据需要通过删除栈顶元素才能入栈,则说明它比栈顶元素大。且是栈顶元素遇到的第一个比之大的元素。**或者说当已经存在栈中的元素,当它出栈时,就可以得到问题需要的答案。

10.png

  • 抽出原始数列中的数值5,因此数比栈顶元素大,故,删除栈顶元素3

11.png

  • 此时,即使删除了栈顶3,数字5还是不能入栈,因栈中元素4比之要小,需要删除栈顶元素4后,数字5方能入栈。

12.png

  • 标志性数字INT_MAX入栈,栈顶元素5出栈,说明5后面没有比之大的数字。

13.png

编码实现:

#include <iostream>
#include <stack>
#include <cstring>
using namespace std;
int main() {
	//INT_MAX :表示 int 最大值
	int nums[6]= {1,4,2,3,5,INT_MAX};
	int res[6];
	memset(res,-1,sizeof(res));
	stack<int> st;
	//递增方式添加数据
	for(int pos=0; pos<6; pos++ ) {
		while(!st.empty() && nums[st.top()]<=nums[pos] ) {
			//如果正入栈的数据大于栈顶数据,栈顶元素出栈
			if(nums[pos]!=INT_MAX)
				res[st.top()] =nums[pos];
			st.pop();
		}
		//入栈
		st.push(pos);
	}
	for(int i=0; i<5; i++) {
		cout<<nums[i]<<":"<<res[i]<<endl;
	}
	return 0;
}

输出结果:

14.png

也可以采用从右向左入栈,且维护栈的单调递增性 。在任一数值入栈时,可以得到下一个比之大的数字。这点与从左向右是不同,从左向右是在出栈时得到结论。

#include <iostream>
#include <stack>
#include <cstring>
using namespace std;

int main() {
	//INT_MAX :表示 int 最大值
	int nums[5]= {1,4,2,3,5};
	int res[5];
	memset(res,-1,sizeof(res));
	stack<int> st;
	//递增方式添加数据
	for(int pos=4; pos>=0; pos-- ) {
		while(!st.empty() && nums[st.top()]<=nums[pos] ) {
			st.pop();
		}
		//入栈之前得到结论 
		res[pos]=st.empty()?-1:nums[st.top()];
		st.push(pos);
	}
	for(int i=0; i<5; i++) {
		cout<<nums[i]<<":"<<res[i]<<endl;
	}
	return 0;
}

从右向左入栈的优点:

  • 因是在入栈时得到结论,所以可以不设置标志数字。

2.2 柱状图中最大矩形

问题描述:

给定 n 个非负整数,用来表示柱状图中各个柱子的高度,每个柱子彼此相邻,且宽度为 1,求柱状图中,能够勾勒出来的矩形的最大面积。

如下图所示:

  • 6个柱子,其高度分别为[2,1,5,6,2,3]

  • 其最大矩形面程应该是:10

21.png

解题思路:

首先想到的是使用穷举法。其思路如下所示:

  • 先求解高度为 2柱子的面积,结果为 2。然后以此柱子为左边界,向右扩展到高度为1的柱子。在 21中取最小值1为新的高度值,并计算面积为2
  • 同理,继续向右边扩展至高度为5的柱子,取最低值1为新高度,计算面积为 3。以此类推,可得到以第一个柱子为左边界可勾勒出来的不同矩形的面积值分别为2,2,3,4,5,6

15.png

  • 以第二个,即高度为1的柱子,为左边界,向右扩展后,其不同矩形面积如下所示:

16.png

  • 以柱高为5的柱子向右扩展。其不同矩形面积。

17.png

  • 最后,以每一个柱子为左边界,向右边扩展,然后求出所有面积中的最大值。

18.png

19.png

20.png

编码实现:

#include <iostream>
#include <stack>
#include <cstring>
using namespace std;

int main() {
	//INT_MAX :表示 int 最大值
	int heights[6]= {2,1,5,6,2,3};
	int maxArea=0;
	int width=1;
	int minHeight=0;
	for(int i=0; i<6; i++) {
		minHeight=heights[i];
		if( maxArea<minHeight*width) {
			maxArea=minHeight*width;
		}
		for(int  j=i+1; j<6; j++ ) {
			//求最小高度
			if( heights[j]<minHeight ) {
				minHeight=heights[j];
			}
			if( maxArea<minHeight*(j-i+1)*width) {
				maxArea=minHeight*(j-i+1)*width;
			}
		}
	}
	cout<<"最大矩形面积:"<<maxArea<<endl;
	return 0;
}

输出结果:

22.png

因穷举法的时间复杂度为O(n^2),当柱子数量很多时,其计算量较多,

此问题可以使用单调栈对代码进行优化。

#include <iostream>
#include <stack>
#include <cstring>
using namespace std;
int main() {
	int heights[6]= {2,1,5,6,2,3};
	int n=sizeof(heights )/4;
	//单调栈方式
	stack<int> s;
	int maxArea=0;
	for(int i=0; i<=n; i++) {
		int curretHeight=(i==n)?-1:heights[i];
		//当前值比栈顶元素小时
		while(!s.empty()&&curretHeight<heights[s.top()]) {
			int num=heights[s.top()];
			s.pop();
			if(!s.empty()) maxArea=max(maxArea,num*(i-s.top()-1));
			else maxArea=max(maxArea,num*i);
		}
		s.push(i);
	}
	cout<<"最大矩形面积:"<<maxArea<<endl;
	return 0;
}

3. 总结

笛卡尔树的构建逻辑中,也使用到了单调栈的存储原理。解决诸多问题时,合理使用单调栈,可让问题的求解过程变得简单易懂。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/587478.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【JY】ABAQUS正交各向异性弹性本构模型

写在前文 材料的线弹性本构模型能够很好的描述处于工作荷载水平下的材料性能情况&#xff0c;后续材料的塑性理论也需要在弹性本构模型的基础上进行开展。由于砌体结构所采用的砌体材料具有明显的正交各项异性&#xff0c;故先从正交各向异性弹性入手&#xff0c;根据弹性理论中…

Java基础篇 | Java开发环境的搭建

✅作者简介&#xff1a;大家好&#xff0c;我是Cisyam&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Cisyam-Shark的博客 &#x1f49e;当前专栏&#xff1a; Java从入门到精通 ✨特色…

中创“六一”公益关爱活动 | 慈善守护童心,爱心筑梦未来

每一个孩子都是一朵花 有的盛开在春天&#xff0c;有的绽放在夏天 每一朵花&#xff0c;都有与众不同的美好 年年盛夏&#xff0c;如约出发&#xff1a; 在第73个“六一国际儿童节”来临之际&#xff0c;中创算力开展“六一公益关爱活动”&#xff0c;希望通过这样一个爱心…

我用低代码结合ChatGPT开发,每天多出1小时摸鱼

&#x1f449;腾小云导读 GPT 出现之后&#xff0c;很多人推测大量的软件都会因为其出现而重写。本文主要是低代码平台与 ChatGPT 结合的一些思考以及实践。期望与各位读者一起搭上 AI 这列快车&#xff0c;为开发提提速&#xff5e; &#x1f449;目录 1 背景 2 Demo 演示 3 思…

2022年软件测试人员调查统计——数据统计

1、软件测试从业人员的年龄分布 测试行业的主力军年龄分布主要是集中在 26-30 岁这个区间&#xff0c;这部分的群体承担着行 业发展的主导力量&#xff0c;占 43.2%。根据数据显示&#xff0c;被调查者中占比最多的是 26-30 岁区间的软件测试从业人员&#xff0c;26-30 岁的测试…

14 种免费 GIS 软件:在开源中绘制世界地图

如果你想绘制一幅世界地图&#xff0c;会选择什么GIS软件呢&#xff0c;ArcGIS、GlobalMapper这些都是国外比较出名的商业GIS软件&#xff0c;当然在国内很容易找到可用的版本&#xff0c;但是也可以使用免费的GIS软件完成所有操作。 这些免费的GIS软件为您提供了完成工作的效…

使用 VSCode SSH 公网远程连接本地服务器开发 - cpolar内网穿透

文章目录 前言视频教程1、安装OpenSSH2、vscode配置ssh3. 局域网测试连接远程服务器4. 公网远程连接4.1 ubuntu安装cpolar内网穿透4.2 创建隧道映射4.3 测试公网远程连接 5. 配置固定TCP端口地址5.1 保留一个固定TCP端口地址5.2 配置固定TCP端口地址5.3 测试固定公网地址远程 转…

Socket(六)

文章目录 1. 构造服务器Socket2. 构造但不绑定端口3. 获得服务器Socket的有关信息4. Socket选项5. SO_TIMEOUT6. SO_REUSEADDR7. SO_RCVBUF8. 服务类型 1. 构造服务器Socket 有四个公共的ServerSocket构造函数 public ServerSocket(int port) throws BindException, IOExcept…

第33步 机器学习分类实战:误判病例分析

填最后一个坑&#xff0c;如何寻找误判的病例。 之前我们在介绍AUC的时候&#xff0c;提到了两个函数&#xff1a;predict和predict_proba&#xff0c;复习一下&#xff1a; auc_test roc_auc_score(y_test, y_testprba) roc_auc_score的参数呢&#xff0c;包括两个&#…

用一杯星巴克的钱,训练自己私有化的ChatGPT

点击蓝字 关注我们 文章摘要&#xff1a;用一杯星巴克的钱&#xff0c;自己动手2小时的时间&#xff0c;就可以拥有自己训练的开源大模型&#xff0c;并可以根据不同的训练数据方向加强各种不同的技能&#xff0c;医疗、编程、炒股、恋爱&#xff0c;让你的大模型更“懂”你….…

想知道如何给游戏视频配音?有三个好方法教给你

现在越来越多的小伙伴会在闲暇之余打打游戏&#xff0c;也会观看一些游戏视频&#xff0c;其中不少游戏视频都有配音。游戏视频配音是一项有趣而富有创造力的任务&#xff0c;它可以让你为游戏画面注入声音&#xff0c;增强观众的体验。无论你是一个游戏爱好者还是一个内容创作…

前端vscode插件bito

GPT-4和ChatGPT越来越火&#xff0c;前端人员是否也能在日常工作中尝试体验其带来的乐趣呢&#xff1f; 答案是可以的&#xff01;安排&#xff01;&#xff01; 今天介绍一款vscode的插件 【bito】。 安装 安装后只需要自己注册一下&#xff0c;创建一个workspace就可以使用…

【满分】【华为OD机试真题2023B卷 JAVAJS】经典屏保

华为OD2023(B卷)机试题库全覆盖,刷题指南点这里 经典屏保 知识点循环迭代编程基础 时间限制:1s 空间限制:256MB 限定语言:不限 题目描述: DVD机在视频输出时,为了保护电视显像管,在待机状态会显示“屏保动画”,如下图所示,DVD Logo在屏幕内来回运动,碰到边缘会反弹…

凡亿教育荣获2023 STM32峰会“生态合作伙伴”奖项

作为中国知名的电子设计在线教育领域的优秀企业&#xff0c;凡亿教育喜迎好消息&#xff01;2023年5月12日&#xff0c;STM32峰会在深圳重磅回归&#xff0c;凡亿教育荣获STM32峰会颁发的“生态合作伙伴”奖项&#xff0c;这一殊荣充分肯定了凡亿教育在STM32生态系统中的杰出贡…

C进阶:数据在内存中的存储(1)

引入&#xff1a; 大家好&#xff0c;感谢大家最近的支持&#xff0c;今天也是开始了C进阶一系列的博文的创作&#xff0c;欢迎大佬们来指点&#xff0c;欢迎来一起沟通&#xff01;&#xff01;&#xff01; 数据类型的介绍 我们都知道&#xff1a;C语言具有以下几种数据类…

拼多多继续ALL IN

2023年注定是中国电商不平凡的一年。 随着网购用户数量见顶&#xff0c;经济形势进入新常态&#xff0c;电商平台已经来到了短兵相接的肉搏战阶段。 此刻的618大促&#xff0c;硝烟弥漫&#xff0c;刀光剑影&#xff0c;电商“决战”似乎是迫在眉睫。对各个平台来说&#xff0c…

SpringBoot:WebSocket实现消息撤回、图片撤回

下面只是讲述一下实现思路&#xff0c;代码基本没有哈&#xff01;有时间单独发表一篇关于websocket的相关操作的博客。 1. 消息撤回、图片撤回 个人觉得关于撤回&#xff0c;需要下述几个过程&#xff1a; 发送的消息的标签上可以定义一个属性&#xff0c;这个属性的值应该是…

18- 弹幕系统设计

1、弹幕系统设计 场景分析&#xff1a;客户端针对某一视频创建了弹幕&#xff0c;发送后端进行处理&#xff0c;后端需要对所有正在观看该视频的用户推送该弹幕。 1.1、实现方式 使用短连接进行通信或使用长连接进行通信。 1.1.1、短连接实现方案 所有观看视频的客户端不断…

单元测试尽量不要区分类与方法

单元测试尽量不要区分类与方法 单元测试是软件开发中非常重要的一环&#xff0c;其主要作用是在开发过程中对代码进行自动化测试与检验&#xff0c;从而保证代码质量&#xff0c;减少错误和缺陷的产生。然而&#xff0c;在实际的单元测试中&#xff0c;很多人会选择将测试分成类…

svg教程-初识svg

第一章 认识svg 简单来说&#xff1a; 位图&#xff1a;放大会失真图像边缘有锯齿&#xff1b;是由像素点组成&#xff1b;前端的 Canvas 就是位图效果。 矢量图&#xff1a;放大不会失真&#xff1b;使用 XML 描述图形。 我在 知乎 上找了一个图对说明一下。 左边是位图&…