【数据结构】单调栈

news2024/11/16 6:45:47

目录

  • 1.概述
  • 2.代码框架
    • 2.1.单调递增栈
    • 2.2.单调递减栈
  • 3.应用
    • 3.1.应用一
    • 3.2.应用二

1.概述

(1)单调栈是一种特殊的栈,它在普通栈的基础上要求从栈顶到栈底的元素是单调的,如果栈中的元素从栈顶到栈底是单调递增的,那么我们将该栈称为单调递增栈 。如果栈中的元素从栈顶到栈底是单调递减的,则称为单调递增栈

(2)单调栈的主要作用在于在时间复杂度为 O(n) 的情况下,找到数组中所有元素的左边或者右边第一个比它小或者大的元素,主要思想在于用空间来换时间,即用单调栈来存储相关信息,这样一来虽然空间复杂度增加了,但是时间复杂度缺降低了。

(3)我们以 nums = [2, 0, 6, 4, 7, 3, -1, 9] 为例,来说明一下单调递增栈的进栈、出栈过程,从左往右开始遍历数组 nums:

第 i 步待插入元素操作栈中元素(栈顶 → 栈底)结果
122 入栈[2]元素 2 的左侧没有比 2 大的元素
200 入栈[0, 2]元素 0 的左侧第一个比 0 大的元素为 2
360、2 出栈,6 入栈[6]元素 6 的左侧没有比 6 大的元素
444 入栈[4, 6]元素 4 的左侧第一个比 4 大的元素为 6
574、6 出栈,7 入栈[7]元素 7 的左侧没有比 7 大的元素
633 入栈[3, 7]元素 3 的左侧第一个比 3 大的元素为 7
7-1-1 入栈[-1, 3, 7]元素 -1 的左侧第一个比 -1 大的元素为 3
79-1、3、7 出栈,9 入栈[9]元素 9 的左侧没有比 9 大的元素

2.代码框架

2.1.单调递增栈

class Solution {
    public void increasingStack(int[] nums) {
        //单调递增栈
        Deque<Integer> incrStack = new ArrayDeque<>();
        for (int i = 0; i < nums.length; i++) {
            //当前元素比栈顶元素小时才能入栈,否则需要先将栈顶元素进行出栈操作
            while (!incrStack .isEmpty() && incrStack .peek() <= nums[i]) {
                incrStack .pop();
                //其它操作
            }
            incrStack .push(nums[i]);
        }
    }
}

2.2.单调递减栈

class Solution {
    public void increasingStack(int[] nums) {
        //单调递减栈
        Deque<Integer> descStack = new ArrayDeque<>();
        for (int i = 0; i < nums.length; i++) {
            //当前元素比栈顶元素大时才能入栈,否则需要先将栈顶元素进行出栈操作
            while (!descStack.isEmpty() && descStack.peek() >= nums[i]) {
                descStack.pop();
                //其它操作
            }
            descStack.push(nums[i]);
        }
    }
}

上述代码理解起来并不算困难,但是有一些细节需要注意:

  • 上面代码中的栈存储的是数组 nums 中元素,但是在有些场景下,则需要存储元素所对应的下标(通过下标可以找到对应的元素,因此存储下标时可使用的信息更多),例如在使用单调栈解决 LeetCode 中的 739.每日温度这题时,存储就是元素的下标,在这种情况下,主要原因在于要求解的问题与元素之间的距离有关
  • 上面的代码只是给出了单调栈最基础的框架,具体如何使用需要根据具体的情况而定。
  • 具体使用单调栈的类别可以总结为一句话:查找比当前元素大的元素就用单调递增栈,查找比当前元素小的元素使用单调递减栈
  • 一般来说,遍历线性表的顺序是从左往右,但在有些情况下,从右往左遍历会更加合适,例如 LeetCode 中的 496.下一个更大元素 I这题。
  • 总的来说,单调栈的代码框架如下所示,其中出栈的条件便决定了使用的单调栈类型:
class Solution {
    public void monotonousStack() {
        Deque<Integer> stack = new ArrayDeque<>();
        for (遍历数组/字符串等线性表) {
            while (!stack.isEmpty() && 出栈的条件) {
                stack.pop();
                //其它操作
            }
            stack.push(元素/元素下标);
        }
    }
}

3.应用

3.1.应用一

下面我们以 LeetCode 中的 739. 每日温度这题为例,来说明单调栈的作用:

在这里插入图片描述

题目要求比较简单,一般来说,暴力穷举法是一种比较容易想到的方法,即使用 2 层 for 循环来求出每一天后面第一次温度高于当天的日期,然后将天数之差的结果存储到数组 answer 中即可。具体代码如下:

class Solution {
    public int[] dailyTemperatures(int[] temperatures) {
        int n = temperatures.length;
        int[] answer = new int[n];
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                if (temperatures[j] > temperatures[i]) {
                    answer[i] = j - i;
                    break;
                }
            }
        }
        return answer;
    }
}

但是上述代码的时间复杂度为 O(n2),那么我们可不可以再优化一下呢?答案是肯定的。通过仔细分析题目可知,题目就是要求解数组 temperatures 中每个元素与其右边第一个比它大的元素之间的距离,这时我们可以使用单调栈来解决本题:

  • 首先,我们需要查找数组中比当前元素大的元素,因此使用单调递增栈,具体体现在 while 循环的第二个判断条件;
  • 其次,由于本题涉及到元素之间的距离问题(即下一个更高温度出现在几天后),因此单调栈中存储元素下标更为合适;
  • 最后,将上面的代码框架应用到本题即可。
class Solution {
	public int[] dailyTemperatures(int[] temperatures) {
	    int length = temperatures.length;
	    int[] answer = new int[length];
	    Deque<Integer> descStack = new ArrayDeque<>();
	    for (int i = 0; i < length; i++) {
	    	/*
	    		temperatures[descStack.peek()] < temperatures[i]: 说明第 i 天的温度高于第 j 天 (j < i,
	    		且 j = descStack.peek()) 的温度,那么此时第 j 天下一个更高温度出现在 i - j 天之后
			*/
	        while (!descStack.isEmpty() && temperatures[descStack.peek()] < temperatures[i]) {
	            answer[descStack.peek()] = i - descStack.peek();
                descStack.pop();
	        }
	        descStack.push(i);
	    }
	    return answer;
	}
}

3.2.应用二

在应用一中,我们可以较为明显地判断出要使用单调栈,但是有些题目可能不太明显,例如 LeetCode 中的 84.柱状图中最大的矩形这题:

在这里插入图片描述

具体细节可以参考本题官方题解,使用单调栈的代码如下所示:

class Solution {
    public int largestRectangleArea(int[] heights) {
        int length = heights.length;
        int maxArea = 0;
        //定义单调栈,此处存储数组元素的下标,保证下标对应的值从栈底到栈顶逐渐递增
        Deque<Integer> stack = new ArrayDeque<>();
        for (int i = 0; i <= length; i++) {
            while (!stack.isEmpty() && (i == length || heights[stack.peek()] >= heights[i])) {
                int j = stack.pop();
                int left = stack.isEmpty() ? 0 : stack.peek() + 1;
                //计算以 heights[j] 为高的矩形面积
                int curArea = (i - left) * heights[j];
                maxArea = Math.max(maxArea, curArea);
            }
            //将当前下标加入到栈中
            stack.push(i);
        }
        return maxArea;
    }
}

最后,大家可以去 LeetCode 上找相关的单调栈的题目来练习,或者也可以直接查看LeetCode算法刷题目录 (Java)这篇文章中的栈章节。如果大家发现文章中的错误之处,可在评论区中指出。

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

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

相关文章

Linux CGroup 原理

Linux CGroup 原理 1、CGroup简介 cgroups是Linux下控制一个&#xff08;或一组&#xff09;进程的资源限制机制&#xff0c;全称是control groups&#xff0c;可以对cpu、内存等资源做精细化控制。 开发者可以直接基于cgroups来进行进程资源控制&#xff0c;比如8核的机器上…

JAVA用tess4j识别复杂的验证码,自定义字库,计算题验证码,jTessBoxEditor,tess4j,验证码识别

JAVA用tess4j识别复杂的验证码&#xff0c;自定义字库&#xff0c;计算题验证码 场景JAVA用tess4j识别文本MAVEN依赖traineddata文件下载识别英文识别中文 JAVA用tess4j识别验证码常见验证码的类型识别 自定义字库&#xff0c;提高识别率下载jTessBoxEditor解压运行准备素材合并…

微信小程序入门笔记

常用技术&#xff1a;开源库 图表&#xff1a;wxcharts-min.js 网络通信&#xff1a;类似 ajax ui: WeUi JSON 配置文件 小程序中&#xff0c;包含唯一的全局配置文件 app.json&#xff0c;以及每个页面的配置文件 page.json。每单页页面相应的 JSON 文件会覆盖与 app.json相同…

门控时钟检查(clock gating check)的理解和设计应用(上)

在笔者的一篇老文Clock Gating之浅见 中&#xff0c;一起探讨过工具处理门控时钟的方法和门控时钟所带来的相关收益和面积代价。除此之外&#xff0c;门控时钟的检查&#xff08;clock-gating check&#xff09;在STA中也有相应的处理方式&#xff0c;通过这篇文章&#xff0c;…

4年测试经验,一问三不知,过于离谱...

公司今年要招人&#xff0c;面倒是面了很多测试&#xff0c;但没有一个合适的。一开始想要的就是中级的水准&#xff0c;也没指望来大牛&#xff0c;当然来了更好&#xff0c;提供的薪资在10-20k,来面试的人有很多&#xff0c;但平均水准真的是让人失望。 看简历时很多都写着3…

【瑞萨MCU】玩转 HMI-Board 之 MDK + RASC 点灯

此前我们已经配置好了 瑞萨 MCU MDK RASC 的开发环境&#xff0c;接下来进入到瑞萨 MCU 的 HelloWorld 环节&#xff0c;使用 MDK 点亮两个 LED 灯。 这次我们使用的是瑞萨和 RT-Thread 联合推出的 HMI-Board 开发板。 HMI-Board 开发板简介 HMI-Board 为 RT-Thread 联合瑞…

3.6 多边形游戏

博主简介&#xff1a;一个爱打游戏的计算机专业学生博主主页&#xff1a; 夏驰和徐策所属专栏&#xff1a;算法设计与分析 1.什么是多边形游戏&#xff1f; 对于多边形游戏&#xff0c;一种特定类型的玩法&#xff0c;即在给定的简单多边形上进行移动、删除顶点或边&#xff…

[图表]pyecharts-3D柱状图

[图表]pyecharts-3D柱状图 先来看代码&#xff1a; import randomfrom pyecharts import options as opts from pyecharts.charts import Bar3D from pyecharts.faker import Fakerdata [(i, j, random.randint(0, 12)) for i in range(6) for j in range(24)] c (Bar3D().…

box-shadow盒子阴影几种用法

box-shadow盒子阴影 语法&#xff1a; 外阴影&#xff1a;box-shadow: X轴 Y轴 Rpx color; 属性说明&#xff08;顺序依次对应&#xff09;&#xff1a; 阴影的X轴(可以使用负值) 阴影的Y轴(可以使用负值) 阴影模糊值&#xff08;大小&#xff09; 阴影的颜色 内阴…

【Linux】LAMP框架的架构与环境配置

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 LAMP框架的架构与环境配置 一、LAMP架构的相关知识1.LAMP组件的主要作用2.LAMP组件安装顺序 二、编译安装Apache httpd服务1.关闭防火墙&#xff0c;将所需软件包传到/opt目录…

00后简直太卷了,让我们这些老油条情何以堪......

前几天我们公司一下子也来了几个新人&#xff0c;这些年前人是真能熬啊&#xff0c;本来我们几个老油子都是每天稍微加会班就打算走了&#xff0c;这几个新人一直不走&#xff0c;搞得我们也不好走。 2023年春招结束了&#xff0c;最近内卷严重&#xff0c;各种跳槽裁员&#x…

ARM-LED灯--蜂鸣器

目录 点亮LED灯 1.开发板介绍 2.cpu控制硬件原理 3.本地开发和交叉开发 4.点灯实验 分析电路图 底板原理图&#xff1a;1&#xff09;电路原理图&#xff0c;LED的控制方式->高电平亮&#xff0c;低电平灭 核心板原理图&#xff1a;2&#xff09;LED与SOC的连接关系-&…

Vue+node.js校园食堂餐厅外卖点餐系统088e4

外卖点餐系统的主要使用者分为管理员和用户&#xff0c;实现功能包括用户管理、菜品分类管理、菜品信息管理、系统管理、订单管理等功能。由于本网站的功能模块设计比较全面&#xff0c;所以使得整个外卖点餐信息管理的过程得以实现。 本外卖点餐系统主要包括二大功能模块&…

Dynamic Connected Networks for Chinese Spelling Check ---- 论文阅读

DCN 动机简介方法动态连接评分器候选生成拼音增强候选生成器 训练损失函数预训练 预测 实验数据集方法比较 消融 Paper&#xff1a;https://aclanthology.org/2021.findings-acl.216.pdf Code&#xff1a;https://github.com/destwang/DCN [Pytorch] 哈工大讯飞 动机 大多数关…

【Linux】Apache 配置与应用

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、构建虚拟 Web 主机1.httpd服务支持的虚拟主机类型 二、基于域名的虚拟主机1.为虚拟主机提供域名解析2.为虚拟主机准备网页文档3.添加虚拟主机配置4.设置访问控制…

FreeRTOS进阶学习

一、FreeRTOS内核控制控制函数&#xff1a; 1、请求任务切换函数&#xff1a; 函数原型&#xff1a;#define taskYIELD() 函数解析&#xff1a;实际上是一个宏定义的函数&#xff0c;调用一次会触发pendSV中断来实现任务切换&#xff1b; /// 2、在任务中进入临界区函数&…

LLM:大模型的正则化

主流大模型使用的Normalization主要有三类&#xff0c;分别是Layer Norm&#xff0c;RMS Norm&#xff0c;以及Deep Norm。 小模型的正则化参考&#xff1a; l1l2正则和dropout正则化[https://youzipi.blog.csdn.net/article/details/75307522] LN和BN归一化 [深度学习&…

【3000字帮你深度剖析数据在内存中的存储】

本节重点 -- 重中之重 数据类型详细介绍 整形在内存中的存储&#xff1a;原码、反码、补码 大小端字节序介绍及判断 浮点型在内存中的存储解析 准备好了&#xff0c;开始啰&#xff0c;在小小的花园里面......最近被这个歌曲洗脑&#xff0c;但是我们并不是要唱歌&#xff0c;而…

linux基础知识学习记录

这里写自定义目录标题 一、 计算机基础知识二 、 Linux操作系统的介绍三、 Linux的安装四、Linux命令使用汇总 一、 计算机基础知识 计算机组成&#xff1a;计算机主要硬件和软件2部分组成。计算机软硬件的概念&#xff1a;硬件是可以看得见的物理实体&#xff0c;软件是运行在…

还在担心期末挂科吗? 期末必备复习资料-----“树“的概念

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;推荐专栏: &#x1f354;&#x1f35f;&#x1f32f;C语言进阶 &#x1f511;个人信条: &#x1f335;知行合一 &#x1f349;本篇简介:>:记录期末复习 数据结构中有关树的一些知识 金句分享: ✨我将…