[数据结构]——单调栈「建议收藏」

[数据结构]——单调栈「建议收藏」单调栈笔者在做leetcode的题(下一个出现的最大数字)时,接触到了单调栈这一种数据结构,经过研究之后,发现单调栈在解决某些问题时出奇的好用,下面是对单调栈的性质和一些典型题目。什么是单调栈?从名字上就听的出来,单调栈中存放的数据应该是有序的,所以单调栈也分为单调递增栈和单调递减栈单调递增栈:数据出栈的序列为单调递增序列单调递减栈:数据出栈的序列为单调递减序列ps:这里一定要注意…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

单调栈

笔者在做leetcode的题(下一个出现的最大数字)时,接触到了单调栈这一种数据结构,经过研究之后,发现单调栈在解决某些问题时出奇的好用,下面是对单调栈的性质和一些典型题目。

什么是单调栈?

从名字上就听的出来,单调栈中存放的数据应该是有序的,所以单调栈也分为单调递增栈单调递减栈

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

模拟单调栈的数据push和pop

模拟实现一个递增单调栈:

现在有一组数10,3,7,4,12。从左到右依次入栈,则如果栈为空入栈元素值小于栈顶元素值,则入栈;否则,如果入栈则会破坏栈的单调性,则需要把比入栈元素小的元素全部出栈。单调递减的栈反之。

  • 10入栈时,栈为空,直接入栈,栈内元素为10。

  • 3入栈时,栈顶元素10比3大,则入栈,栈内元素为10,3。

  • 7入栈时,栈顶元素3比7小,则栈顶元素出栈,此时栈顶元素为10,比7大,则7入栈,栈内元素为10,7。

  • 4入栈时,栈顶元素7比4大,则入栈,栈内元素为10,7,4。

  • 12入栈时,栈顶元素4比12小,4出栈,此时栈顶元素为7,仍比12小,栈顶元素7继续出栈,此时栈顶元素为10,仍比12小,10出栈,此时栈为空,12入栈,栈内元素为12。

单调栈的伪代码

stack<int> st;
//此处一般需要给数组最后添加结束标志符,具体下面例题会有详细讲解
for (遍历这个数组)
{ 
   
	if (栈空 || 栈顶元素大于等于当前比较元素)
	{ 
   
		入栈;
	}
	else
	{ 
   
		while (栈不为空 && 栈顶元素小于当前元素)
		{ 
   
			栈顶元素出栈;
			更新结果;
		}
		当前数据入栈;
	}
}

单调栈的应用

单调栈的应用我们直接拿一些具体的题来对照应用:

1.视野总和

描叙:有n个人站队,所有的人全部向右看,个子高的可以看到个子低的发型,给出每个人的身高,问所有人能看到其他人发现总和是多少。
输入:4 3 7 1
输出:2
解释:个子为4的可以看到个子为3的发型,个子为7可以看到个子为1的身高,所以1+1=2
思路:观察题之后,我们发现实际上题目转化为找当前数字向右查找的第一个大于他的数字之间有多少个数字,然后将每个          结果累计就是答案,但是这里时间复杂度为O(N^2),所以我们使用单调栈来解决这个问题。

1.设置一个单调递增的栈(栈内0~n为单调递减)
2.当遇到大于栈顶的元素,开始更新之前不高于当前人所能看到的值
在这里插入图片描述

int FieldSum(vector<int>& v)
{ 
   
	v.push_back(INT_MAX);/这里可以理解为需要一个无限高的人挡住栈中的人,不然栈中元素最后无法完全出栈
	stack<int> st;
	int sum = 0;
	for (int i = 0; i < (int)v.size(); i++)
	{ 
   
		if (st.empty() || v[st.top()] > v[i])//小于栈顶元素入栈
		{ 
   
			st.push(i);
		}
		else
		{ 
   
			while (!st.empty() && v[st.top()] <= v[i])
			{ 
   
				int top = st.top();//取出栈顶元素
				st.pop();
				sum += (i - top - 1);//这里需要多减一个1
			}
			st.push(i);
		}
	}
	return sum;
}

2.柱状图中的最大矩形

https://leetcode-cn.com/problems/largest-rectangle-in-histogram/
在这里插入图片描述
在这里插入图片描述
思路:当前的数字可以向两边拓展,遇到比自己大的就接着拓展,小的就停止,然后用自己的高度乘以拓展的宽度,每次都         跟新最大面积,时间复杂度同样为O(N^2),所以我们接着借助单调栈

上面使用了单调递增栈,这里我们通过这道例题来使用一下单调递减栈

1.设置一个单调递减的栈(栈内0~n为单调递增)
2.当遇到小于栈顶元素的值,我们开始更新数据,因为有可能最大面积就会出现在栈中的序列里
3.牢记栈中数据永远是有序的,这个问题比较复杂,所以读者不妨对照着代码来理解问题

int largestRectangleArea(vector<int>& heights) { 
   
	heights.push_back(-1);/同理,我们希望栈中所有数据出栈,所以给数组最后添加一个负数
	stack<int> st;
	int ret = 0, top;
	for (int i = 0; i < heights.size(); i++)
	{ 
   
		if (st.empty() || heights[st.top()] <= heights[i])
		{ 
   
			st.push(i);
		}
		else
		{ 
   
			while (!st.empty() && heights[st.top()] > heights[i])
			{ 
   
				top = st.top();
				st.pop();
				//i-top指的是当前矩形的宽度,heights[top]就是当前的高度
				//再次强调栈中现在为单调递增
				int tmp = (i - top)*heights[top];
				if (tmp > ret)
					ret = tmp;
			}
			st.push(top);
			heights[top] = heights[i];
		}
	}
	return ret;
}

很多读者不太懂下图这两句代码:
在这里插入图片描述
假设遇到了小于栈顶的数据,我们需要判断下图中哪个矩形更大,并且跟新数据,这里应该都可以理解,我们将图中三个数据标记为0,1,2.接着往下看
在这里插入图片描述
因为需要保持栈中递增的属性,所以栈中只有i一个数据:
在这里插入图片描述
但是对于当前元素来说下标为0,1的元素都比他大,所以那么就意味着它可以向左延申扩大矩形:像下图那样
在这里插入图片描述
但是我们为了保持栈中的递增属性,并且可以让i可以向左拓展,我们索性修改了i的下标,将他修改为最左边的top下标,所以当我们下次需要以他为基准获取矩形面积时就像这样
在这里插入图片描述
所以假设我们数组中的4个数据(实际是5个,最后一个数字用来出栈所有数据)全部访问完时:如下面的方式计算矩形
在这里插入图片描述
ps:如果有的同学还是不清楚,可以用自己的编译器调试一下。

3.求最大区间

描述:给出一组数字,求一区间,使得区间元素和乘以区间最小值最大,结果要求给出这个最大值和区间的左右端点
输入:3 1 6 4 5 2
输出:60
       3 5
解释:将3到5(6+4+5)这段区间相加,将和与区间内最小元素相乘获得最大数字60
思路:使用暴力解法求出所有区间,再求出区间的最小值相乘跟新数据,并不是一种很好的算法,所以经过上面俩题的磨         炼,此时我们应该使用一个单调递减栈

1.设置一个单调递减的栈(栈内0~n为单调递增)
2.当遇到小于栈顶元素的值,我们开始更新数据,因为当前遇到的值一定是当前序列最小的

int GetMaxSequence(vector<int>& v)
{ 
   
	stack<int> st;
	vector<int> vs(v.size()+1);
	vs[0] = 0;
	for (int i = 1; i < vs.size(); i++)
	{ 
   
			vs[i] = vs[i - 1] + v[i-1];
	}
	v.push_back(-1);
	int top, start, end, ret = 0;
	for (int i = 0; i < v.size(); i++)
	{ 
   
		if (st.empty() || v[st.top()] <= v[i])
		{ 
   
			st.push(i);
		}
		else
		{ 
   
			while (!st.empty() && v[st.top()] > v[i])
			{ 
   
				top = st.top();
				st.pop();
				int tmp = vs[i] - vs[top];
				tmp = tmp * v[top];
				if (tmp > ret)
				{ 
   
					ret = tmp;
					start = top+1;
					end = i;
				}
			}
			st.push(top);
			v[top] = v[i];//与第二题相同的道理,将当前数据的更改最左的top下标,防止出现比当前数据更小的数据
			//这句在这道题里真的超级难理解,但是只要你有耐心相信你可以理解的
		}
	}
	return ret
}

总结

单调栈是帮助我们完成算法的一个数据结构,很多的题中还是单调栈的身影,更多需要单调栈的题就希望读者自己去发现啦,文章如果有什么问题或者建议希望广大读者们可以提出。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/190493.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 成功解决IDEA2022 Plugins 连不上、打不开「建议收藏」

    成功解决IDEA2022 Plugins 连不上、打不开「建议收藏」解决IntelliJIDEA2020.1插件中心一直打不开

    2022年10月21日
  • MATLAB GUI显示图片的方法

    MATLAB GUI显示图片的方法前言  在MATLAB的命令行中显示图片或者数据,十分简单,仅通过imshow,plot或者imagesc等函数即可。  而在MATLABGUI中显示图片,通常需要借助Axes控件来实现。相比而言,多一些操作。在GUI中显示图片  创建一个空白的界面  在GUIDE中,添加一个按钮,然后再添加一个Axes控件,适当调整两者比例。然后在Button的回调函数中添加如下代码%…

  • java流操作对文件的分割和合并

    学习文件的输入输出流,自己做一个小的示例,对文件进行分割和合并。

  • Python表白代码:太秀了,用过的人都找到了对象…【满屏玫瑰盛开】

    Python表白代码:太秀了,用过的人都找到了对象…【满屏玫瑰盛开】导语暗恋让人受尽委屈!一开始,你是我的秘密,我怕你知道,又怕你不知道,又怕你知道装作不知道!这大概就是暗恋的感受吧,可若是双向奔赴,那简更是甜蜜度爆表,快同小编吃下这波狗粮!跟着上一期的玫瑰花花样表白之后,小编新出了2款新型升级之后的表白代码!花样表白总有一款是你喜欢的!效果满分~正文还是熟悉的配方!熟悉的味道!盛开的蓝玫瑰效果如下:附源码:t.setup(800,800)t.hideturtle()t.speed(11)t.penup().

  • CCS 8.00 软件中视窗的应用

    1.多种视窗通过CCS界面View可以看到存在多种视窗;memorybrowser在调试中可以查看SARAM中对应地址的数值;Register:DSP各存储模块的变化(类似系统关键字);Expressions和Variables是运用最多的,方便看程序中定义的变量。Disasembly方便查看C语言和汇编语言对应关系;Breakpoint方便对断点进行管理。2.断点管理断点管理试图:可以单一或者批量删除断点;屏蔽断点;启动断点需要在复选框中打钩。3.变量变化无论是regis

  • Java.Utils:精确运算工具类

    Java.Utils:精确运算工具类packagecom.boob.common.utils;importjava.math.BigDecimal;/***@description:精确运算工具类*@author:boob*@since:2020/2/9*/publicclassMathUtils{publicMathUtils(){}/**…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号