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

[数据结构]——单调栈「建议收藏」单调栈笔者在做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)
blank

相关推荐

  • MODIS数据介绍

    MODIS数据介绍转自:http://blog.sina.com.cn/s/blog_53e9bb570101jv55.html一、Modis数据资源总体介绍&nbsp;1999年2月18日,美国成功地发射了地球观测系统(EOS)的第一颗先进的极地轨道环境遥感卫星Terra。它的主要目标是实现…

  • Nfs固定端口 nfs 端口公网映射

    Nfs固定端口 nfs 端口公网映射Nfs固定端口nfs端口公网映射 postedon2019-09-2515:46秦瑞It行程实录阅读(…)评论(…)编辑…

  • [微信开发] – 微信开放平台的扫码登录

    [微信开发] – 微信开放平台的扫码登录首先开通微信开放平台,申请网站应用,交钱通过后可以使用appid和appsecret(和公众平台的不同)之后可以生成扫码登录的二维码了,主要根据开放平台提供的api请求链接和appid,appsecret<%–测试首页–%><%@pagecontentType=”text/html;charset=UTF-8″language=”java”%&…

  • 2015智能家居市场十大收购案

    2015智能家居市场十大收购案一方面,由于智能家居行业发展前景的无可**,大企业纷纷拓展智能家居业务;另一方面,资本寒冬来袭,小型智能家居企业纷纷求抱大腿。一时间,智能家居行业收购案频频发生。此等收购案背后真正企图是什么?又会给智能家居行业的发展带来哪些影响?  高通24亿美元收购英国CSR进军智能家居2015年8月13日,全球最大智能手机芯片供应商高通正式宣布以24亿美元完成对…

  • NABC模型进行需求分析

    NABC模型进行需求分析

  • pkl文件是什么_python pkl文件

    pkl文件是什么_python pkl文件这里只介绍关于字典类型和列表类型的数据的存储,其他格式的请君自行探索。1、字典类型importpickle#首先导入这个库,没有安装的话,自行百度,很简单dict_data={“name”:[“张三”,”李四”]}withopen(“dict_data.pkl”,’wb’)asfo:#将数据写入pkl文件pickle.dump(dict_data,fo)with…

发表回复

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

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