单调栈介绍和使用

news2025/1/21 0:52:19

前言:

今天来讲一下单调栈,它定义是非常简单的,首先栈是一种先进后出、后进先出的数据结构。而单调栈,就是说栈中的元素是严格单调递增或者递减的。它主要用来解决的问题:找到前一个或者后一个的最大或者最小元素。属于一种空间换时间的思想,通常用来把O(n^2)的时间复杂度降低到O(n)。

典型的做法:

假设我们是要找比当前元素大的元素,那么栈内的元素就是递增的(从栈顶往栈底方向)。

当元素大于栈顶的元素,就把栈顶的元素给替换成当前元素;

当元素小于等于栈顶元素,就直接入栈。

这样处理后,栈内就一直保持着一个从栈顶往栈底方向递增的单调栈。

注意:一般栈内储存的都是元素的下标而不是元素的值,因为下标可以直接找到值,而值不能直接找到下标。

所以如果是计算两个元素之间的距离的题目,储存值就没法算了。

因此单调栈里面说的单调性指的是下标对应的元素值单调的。

实例:

LeetCode:739-每日温度

给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。

示例 1:
输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]

示例 2:
输入: temperatures = [30,40,50,60]
输出: [1,1,1,0]

示例 3:
输入: temperatures = [30,60,90]
输出: [1,1,0]
 

提示:
1 <= temperatures.length <= 105
30 <= temperatures[i] <= 100

首先这道题,朴素的想法就是暴力处理,就是数组temperatures里面每个元素遍历一遍,假设右边第一个大于自己的元素下标是j,当前元素下标是i,然后j-i的值放入到answer[i]中就可以了。因为有n个元素,所以时间复杂度是O(n2),空间复杂度就是开辟的answer数组,是O(n)。这样做在数据量比较小的时候是可以的,如果temperaturess长度到达了109这种,就会超时。

如果我们使用单调栈,就可以将时间复杂度降低到O(n),但是同时要开辟一个n长度的栈,空间复杂度上升了。所以说单调栈是一种用空间换时间的做法,不过一般题目空间的要求都不高,对时间要求比较高,而且只开辟了一个n长度的栈,占用也不大,因此单调栈还是很实用的。

我们看一下这道题是怎么用单调栈处理的,以示例1为例子:

我们创建了一个栈stack

image-20231006114130491

1.遍历到下标0,值73,栈是空的,0入栈

image-20231006114241931

2.遍历到下标1,值74,发现74大于栈顶元素0对应的值73,

下标0出栈,下标1-下标0等于1,放入到answer[0]

下标1入栈

image-20231006114935771

image-20231006115339470

3.遍历到下标2,值75,发现75大于栈顶元素1对应的值是74,

下标1出栈,下标2-下标1等于1,放入到answer[1]

下标2入栈

image-20231006115200729

image-20231006115253991

4.遍历到下标3,值71,发现71小于栈顶元素2对应的值是75,下标3入栈

image-20231006115453443

image-20231006114721866

5.遍历到下标4,值69,发现69小于栈顶元素3对应的值是71,下标4入栈

image-20231006115558937

image-20231006115653635

6.遍历到下标5,值72,发现72大于栈顶元素4对应的值是69,下标4出栈

下标5-下标4等于1,放入到answer[4]

发现72大于栈顶元素3对应的值71,下标3出栈

下标5-下标3等于2,放入到answer[3]

发现72小于栈顶元素2对应的值75,下标5入栈

image-20231006115826006

image-20231006115912409

image-20231006115958207

image-20231006120035654

7.遍历到下标6,值76,发现76大于栈顶元素5的值72,栈顶元素5出栈

下标6-下标5等于1,放入到answer[5]

发现76大于栈顶元素2的值75,栈顶元素2出栈

下标6-下标2等于4,放入到answer[2]

栈内没有元素了,下标6入栈

image-20231006120233979

image-20231006120313697

image-20231006120345625

8.遍历到下标7,值73,发现73小于栈顶元素6的值76,下标7入栈。

image-20231006120512733

image-20231006120546057

9.最后因为answer默认值是0,下标7和下标6没有

代码

public class DailyTemperatures_739 {


    public int[] dailyTemperatures(int[] temperatures) {
        Stack<Integer> stack = new Stack<>();
        int[] answer = new int[temperatures.length];
        for (int i = 0; i < temperatures.length; i++) {
            // 当前元素大于栈顶元素
            while (!stack.isEmpty() && temperatures[stack.peek()] < temperatures[i]) {
                int index = stack.pop();
                answer[index] = i - index;

            }
            // 栈是空的 或者 当前元素小于或者等于栈顶 就放入栈中
            stack.push(i);
        }
        return answer;
    }
    
}

代码其实很简单,但是要想到用单调栈来解决类似的问题,还需要多练习,因为很多题目不会直接说就是要找后面比当前元素大的元素,需要自己理解题意之后判断出来。

练习

再看一道单调栈的题目,LeetCode-美丽塔II-2866是上上周周赛的一道题目。

给你一个长度为 n 下标从 0 开始的整数数组 maxHeights 。
你的任务是在坐标轴上建 n 座塔。第 i 座塔的下标为 i ,高度为 heights[i] 。
如果以下条件满足,我们称这些塔是 美丽 的:

1 <= heights[i] <= maxHeights[i]
heights 是一个 山状 数组。
如果存在下标 i 满足以下条件,那么我们称数组 heights 是一个 山状 数组:

对于所有 0 < j <= i ,都有 heights[j - 1] <= heights[j]
对于所有 i <= k < n - 1 ,都有 heights[k + 1] <= heights[k]
请你返回满足 美丽塔 要求的方案中,高度和的最大值 。

 

示例 1:
输入:maxHeights = [5,3,4,1,1]
输出:13
解释:和最大的美丽塔方案为 heights = [5,3,3,1,1] ,这是一个美丽塔方案,因为:
- 1 <= heights[i] <= maxHeights[i]  
- heights 是个山状数组,峰值在 i = 0 处。
13 是所有美丽塔方案中的最大高度和。

示例 2:
输入:maxHeights = [6,5,3,9,2,7]
输出:22
解释: 和最大的美丽塔方案为 heights = [3,3,3,9,2,2] ,这是一个美丽塔方案,因为:
- 1 <= heights[i] <= maxHeights[i]
- heights 是个山状数组,峰值在 i = 3 处。
22 是所有美丽塔方案中的最大高度和。

示例 3:
输入:maxHeights = [3,2,5,5,2,3]
输出:18
解释:和最大的美丽塔方案为 heights = [2,2,5,5,2,2] ,这是一个美丽塔方案,因为:
- 1 <= heights[i] <= maxHeights[i]
- heights 是个山状数组,最大值在 i = 2 处。
注意,在这个方案中,i = 3 也是一个峰值。
18 是所有美丽塔方案中的最大高度和。
 

提示:
1 <= n == maxHeights <= 105
1 <= maxHeights[i] <= 109

解析:

这个题目就没法直接看出来是用单调栈,要分析题目之后才能想到,因为要得到一个山状数组,山顶的左边是严格递增的,山的右边是严格递减的。所以我们可以用两次单调栈来解决这个问题,同时因为这个题目最后求得是元素的和,所以我们还需要两个数组来储存前缀和和后缀和,最大值就是前缀和pre数组和后缀和surf相加的最大值。

我们先假设山顶在最右边,前缀和的数组pre,就要储存从0到n-1的严格递增的前缀和,用stack来处理遍历过程中的元素。如果栈顶元素是比当前元素要大的,就说明当前元素小于栈顶元素值,要保持严格递增,就需要把原来stack中得元素全部弹出去,以当前元素作为山顶。

stack中有值的情况:

前缀和 = 小于等于当前元素的和 + (被弹出元素到当前元素的距离)* 当前元素值

stack中没有元素的情况:

前缀和 = 当前元素值 * (当前元素下标 + 1), 这里可以想象如果数组maxHeights是3,2,那么就是2 * (下标 1 + 1)

计算完左边的数组之后,在计算右边的,右边本来是严格递减,但是如果我们从右往左遍历也跟pre数组一样是严格递增。

方便处理我们就改完总有往左遍历,只不过反过来处理下标的时候要注意一下。

最后就是Java会有溢出的情况,要注意下int到long的转换,要不然就会收到几发WA。

public class BeautifulTowersII_2866 {

    public long maximumSumOfHeights(List<Integer> maxHeights) {
        if (maxHeights.size() == 1) {
            return maxHeights.get(0);
        }
        Stack<Integer> stack = new Stack<>();
        int n = maxHeights.size();
        long ans = 0;
        long[] pre = new long[n];
        // 山状数组左边的递增数组 栈顶到栈底是递减的
        for (int i = 0; i < n; i++) {
            // 栈不为空 并且 栈顶元素是大于当前元素就要处理
            while(!stack.isEmpty() && maxHeights.get(stack.peek()) > maxHeights.get(i)) {
                stack.pop();
            }

           if (!stack.isEmpty()) {
               int t = stack.peek();
               pre[i] = pre[t] + (long)(i - t) * maxHeights.get(i) ;
           } else {
               pre[i] = (long)(i + 1) * maxHeights.get(i) ;
           }

            stack.push(i);

        }

        stack.clear();
        long[] surf = new long[n];
        // 山状数组的右边 递减数组 栈顶到栈底是递增的 我们从右往左遍历就可以和pre数组一样的处理方式
        for (int i = n - 1; i >= 0; i--) {
            while(!stack.isEmpty() && maxHeights.get(stack.peek()) > maxHeights.get(i)) {
                 stack.pop();
            }

            if (!stack.isEmpty()) {
                int t = stack.peek();
                surf[i] = surf[t] + (long)(t - i) * maxHeights.get(i) ;
            } else {
                surf[i] = (long)(n - i) * maxHeights.get(i) ;
            }

            stack.push(i);

        }

        for (int i = 0; i < n - 1; i++) {
            ans = Math.max(ans, pre[i] + surf[i+1]);
        }
        return ans;
    }
}

相关题目:

题目难度
496. 下一个更大元素 I简单
503. 下一个更大元素 II中等
581. 最短无序连续子数组中等
654. 最大二叉树中等
316. 去除重复字母中等
402. 移掉 K 位数字中等
456. 132 模式中等
42. 接雨水困难
84. 柱状图中最大的矩形困难
321. 拼接最大数困难

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

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

相关文章

力扣 -- 446. 等差数列划分 II - 子序列

解题步骤&#xff1a; 参考代码&#xff1a; class Solution { public:int numberOfArithmeticSlices(vector<int>& nums) {int nnums.size();//把元素和它对应的所有下标绑定存放到哈希表中unordered_map<double,vector<int>> hash;for(int i0;i<n;…

Day-08 基于 Docker安装 Nginx 镜像-反向代理

此时静态页面网站已经部署上了&#xff0c;但是还是会显示一个端口8080出来&#xff0c;就十分不美观&#xff01; ps: 怎么把端口干掉呢&#xff1f;换成XXXXX.com/demo1 或者 XXXXX.com/demo2这种效果呢&#xff1f; eg: 可以使用 Nginx的反向代理实现&#xff01; 1.开始反…

【算法速查】万字图解带你快速入门八大排序(下)

君兮_的个人主页 即使走的再远&#xff0c;也勿忘启程时的初心 C/C 游戏开发 Hello,米娜桑们&#xff0c;这里是君兮_&#xff0c;首先在这里祝大家中秋国庆双节同乐&#xff01;&#xff01;抓住假期的小尾巴&#xff0c;今天来把算法速查的八大排序的后续写完&#xff0c;当…

Go 复合数据类型之结构体与自定义类型

Go 复合数据类型之结构体与自定义类型 文章目录 Go 复合数据类型之结构体与自定义类型一、类型别名和自定义类型1.1 类型定义&#xff08;Type Definition&#xff09;简单示例 1.2 类型别名简单示例 1.3 类型定义和类型别名的区别 二、结构体2.1 结构体介绍2.2 结构体的定义2.…

MySQL视图、用户管理

目录 视图概念和操作 视图基本操作 视图规则和限制 用户管理 用户 视图概念和操作 什么是视图&#xff1f; 视图是一个虚拟表&#xff0c;由一个或多个基本表的查询结果组成&#xff08;视图是存储在数据库中的查询的SQL 语句&#xff0c;不在数据库中以存储的数据值集形式…

VC6 MFC Dialog as apllication 编程

MFC框架虽然古老&#xff0c;但编程还是方便多了&#xff0c;在操控界面控件的基础上&#xff0c;平时在Console模式习练的类可以融入到MFC中&#xff0c;开发应用程序还是比较方便的。下图是习练程序的界面。 说明&#xff1a; 一个EDIT框&#xff0c;一个Label框&#xff1b…

Linux进程相关管理(ps、top、kill)

目录 一、概念 二、查看进程 1、ps命令查看进程 1&#xff09;ps显示某个时间点的程序运行情况 2&#xff09;查看指定的进程信息 2、top命令查看进程 1&#xff09;信息统计区&#xff1a; 2&#xff09;进程信息区 3&#xff09;交互式命令 三、信号控制进程 四、…

假期AI新闻热点:亚运会Al技术亮点;微软GPT-4V论文精读;Perplexity推出pplx-api;DALL-E 3多渠道测评 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f525; 科技感拉满&#xff0c;第19届杭州亚运会中的Al技术亮点 八年筹备&#xff0c;杭州第19届亚运会开幕式于9月23日晚隆重举行&#xff0…

口袋参谋:如何有效地监测你的竞争对手!

​在淘宝天猫上开店&#xff0c;竞争是非常大的&#xff0c;那么就会出现许多同样的产品&#xff0c;如果想要在竞争中胜出&#xff0c;就需要多去研究同行的数据&#xff0c;知己知彼&#xff0c;百战百胜。 掌握竞争对手数据目的主要是有2个&#xff1a; 1、掌握对手是怎么起…

【二叉树】的实现

&#x1f4d9;作者简介&#xff1a; 清水加冰&#xff0c;目前大二在读&#xff0c;正在学习C/C、Python、操作系统、数据库等。 &#x1f4d8;相关专栏&#xff1a;C语言初阶、C语言进阶、C语言刷题训练营、数据结构刷题训练营、有感兴趣的可以看一看。 欢迎点赞 &#x1f44d…

竞赛选题 深度学习 python opencv 动物识别与检测

文章目录 0 前言1 深度学习实现动物识别与检测2 卷积神经网络2.1卷积层2.2 池化层2.3 激活函数2.4 全连接层2.5 使用tensorflow中keras模块实现卷积神经网络 3 YOLOV53.1 网络架构图3.2 输入端3.3 基准网络3.4 Neck网络3.5 Head输出层 4 数据集准备4.1 数据标注简介4.2 数据保存…

中断:ZYNQ

整个中断系统架构中&#xff0c;只包含以下三种中断&#xff1a;  软件生成中断(Software Generated Interrupts&#xff0c;SGI)  私有外设中断(Private Peripheral Interrupts&#xff0c;PPI)  共享外设中断(Shared Peripheral Interrupts&#xff0c;SPI) 每种中断…

华为云云耀云服务器L实例评测|云耀云服务器L实例部署DjangoBlog个人博客系统

华为云云耀云服务器L实例评测&#xff5c;云耀云服务器L实例部署DjangoBlog个人博客系统 一、云耀云服务器L实例介绍1.1 云耀云服务器L实例简介1.2 云耀云服务器L实例特点 二、DjangoBlog介绍2.1 DjangoBlog介绍2.2 DjangoBlog特点 三、本次实践介绍3.1 本次实践简介3.2 本次环…

java socket实现代理Android App

实现逻辑就是转发请求和响应。 核心代码 // 启动代理服务器private void startProxyServer() {new Thread(new ProxyServer()).start();}// 代理服务器static class ProxyServer implements Runnable {Overridepublic void run() {try {// 监听指定的端口int port 8098; //一…

AutoGen - 多个Agent开发LLM应用的框架

文章目录 关于安装使用关于 Enable Next-Gen Large Language Model Applications 用多个Agent开发LLM应用的框架,这些agent可相互交流以解决任务。 官网:https://microsoft.github.io/autogen/github : http://github.com/microsoft/autogendiscord : https://discord.com/i…

LVGL_基础控件滚轮roller

LVGL_基础控件滚轮roller 1、创建滚轮roller控件 /* 创建一个 lv_roller 部件(对象) */ lv_obj_t * roller lv_roller_create(lv_scr_act()); // 创建一个 lv_roller 部件(对象),他的父对象是活动屏幕对象// 将部件(对象)添加到组&#xff0c;如果设置了默认组&#xff0c…

buuctf-crypto 1

rsarsa 题目描述 Math is cool! Use the RSA algorithm to decode the secret message, c, p, q, and e are parameters for the RSA algorithm.p 96484230290105156765905517400104265349457376392357398006439893520398525072984913995610350091634270503701075707336333…

Java循环队列

目录 一、循环队列 二、设计循环队列 一、循环队列 队列&#xff1a;只能在一端进行插入数据操作&#xff0c;另一端进行删除数据操作的特殊线性表&#xff0c;是一种先进先出的存储结构 插入操作的一端为队尾&#xff0c;删除操作的一端为队头 在线性队列中&#xff0c;一…

leetcode 62. 不同路径、63.不同路径||

62. 不同路径 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&…

课题学习(三)----倾角和方位角的动态测量方法(基于陀螺仪的测量系统)

一、内容介绍 该测量系统基于三轴加速度和三轴陀螺仪&#xff0c;安装在钻柱内部&#xff0c;随钻柱一起旋转&#xff0c;形成捷联惯性导航系统&#xff0c;安装如下图所示&#xff1a;   假设三轴加速度和陀螺仪的输出为: f b [ f x f y f z ] T f^b\begin{bmatrix}f_{x} …