单调栈和单调队列可以很简单

news2024/12/30 3:09:45

大家好,我是 方圆。我觉得单调栈和单调队列的题目很有特点,而且理解了它的特点能够很轻松容易地解决一系列题目,所以决定用这篇帖子记录一下,也想以此来帮助在刷同样类型题目的朋友们。如果大家想要找刷题路线的话,可以参考 Github: LeetCode。

1. 单调栈

单调栈本质上是 维护数组中的元素为单调序列,数组中的元素 要么 符合单调性顺利进栈,要么 不符合单调性而将栈中其他元素“挤走”再进栈,使得栈中序列始终满足单调性。

理解这一点很重要,我们以单调递增栈为例,如果出现了比栈顶元素 的值,即不符合当前栈中序列单增特性的值,那么它会使所有比它大的值出栈,而 该值便是接下来要连续出栈元素右侧最近的小值,比该值大的栈元素都出栈完毕后,该值进栈,使得栈中的序列仍然满足单调递增。

如果题目有 在连续序列中找元素左/右侧最近的大/小值 的特点,我们就可以使用单调栈来求解,找最近的小值的单调递增栈模板如下,注意入栈的是数组元素的 索引 而不是元素值:

Stack<Integer> stack = new Stack<>();

for (int i = 0; i < nums.length; i++) {
    while (!stack.isEmpty() && nums[i] < nums[stack.peek()]) {
        int index = stack.pop();
        
        // 关于 index 的特殊处理
        process();
    }
    // 索引入栈
    stack.push(i);
    
    // 处理逻辑
    process1();
}

这个模板其实很好记,根据想找小值还是找大值来确定模板中的 while 条件,如果找小值则使用小于号,则 nums[i] < nums[stack.peek()],如果找大值则使用大于号,则 nums[i] > nums[stack.peek()],再根据题意判断是否需要给大于小于号添加上等号(这一点考虑是在有重复值出现的情况下)。

说了这么多,其实我们只需要考虑 要找的是最近的小值还是大值,写对应的模板解题即可,即使你没明白为什么单调栈能找元素最近的大/小值(应该自己 Debug 学习一下)也没关系,只要使用了单调栈它就有这个性质,其他的全是虚妄…

相关题目
  • 1475. 商品折扣后的最终价格 简单

  • 739. 每日温度 中等

  • 901. 股票价格跨度 中等

本题其实是逆序遍历数组找左侧最近的大值的题目,能理解这一点就很简单。

  • 496. 下一个更大元素 I 简单

如果不用模拟法而是一定要采用单调栈法的话,我觉得这道题甚至比下一道题要难一些。

  • 503. 下一个更大元素 II 中等

  • 42. 接雨水 困难

接雨水是这些题中比较困难且经典的单调栈应用题,它的要点是找每个柱子两端最近的高柱子,这样才能接到雨水。而找高柱子正对应了要找大值的特点,可以使用单调递减栈模板来解决,正序遍历数组找到能 元素右侧最近的高柱子,而找到高柱子该元素需要出栈,若出栈后栈中还有元素的话,那么该元素为 出栈元素左侧最近的高柱子,所以我们可以得出结论 单调栈能够同时找到元素左右两侧最近的大值/小值。这样,我们就能找到当前柱子左右两边的高柱子,也就可以计算面积了。

1.1 计算当前值作为区间最大值/最小值的最大区间范围

该类型题目不直接要求找某元素左/右侧最近的大/小值,而是利用单调栈能找到某元素最近的大值/小值的特点来 确定当前元素作为区间内最大值/最小值时的区间范围(注意其中的关键字,是作为),以此来计算该元素对题解的"贡献"。

我们初始化每个元素的默认区间范围如下,这是该元素作为极值时的特殊情况:

int[] left = new int[nums.length];
Arrays.fill(left, -1);
int[] right = new int[nums.length];
Arrays.fill(right, nums.length);

以当前元素作为区间内最大值为例,记录每个元素能到达的左侧/右侧的最远距离:

Stack<Integer> stack = new Stack<>();

// 正序遍历计算上界
for(int i = 0; i < nums.length; i++) {
    while (!stack.isEmpty() && nums[i] >= nums[stack.peek()]) {
        right[stack.pop()] = i;    
    }  
    stack.push(i);
}
stack.clear();
// 逆序遍历计算下界
for (int i = nums.length - 1; i >= 0; i--) {
    while (!stack.isEmpty() && nums[i] > nums[stack.peek()]) {
        left[stack.pop()] = i;
    }
    stack.push(i);
}

注意,记录的区间范围为全开区间,所以如果在计算区间长度时需要减 1,而且当题目中没有规定所有值都不同时,需要为其中一个(正序或逆序)遍历条件增加等号,避免发生“重复统计”。

相关题目
  • 795. 区间子数组个数 中等

本题是需要找到每个元素作为区间最大值的区间范围,并使用 乘法原理 计算出所有包含该元素的子数组数量

我们以如下例子来解释什么是重复统计:

单调栈区间最值范围.png

在不加等号的情况下,如果两个相同的值间不存在比它们还大的值,那么它们在统计自己的区间范围时会越过对方的位置,如上图所示,(无等号情况时)两个 3 的区间范围会相同,那么统计的子数组必然存在重复。如果我们能在正序或逆序遍历添加上等号,那么我们统计的区间范围便成了“半开半闭”的范围,那么它们一侧的区间范围相同,一侧的区间范围不同,这样便不会组合出重复的子数组。

PS:我暂时没想到更好的解释办法,如果大家有更好的理解可以写在评论区

  • 907. 子数组的最小值之和 中等

本题需要找到每个元素作为区间内最小值的区间范围,使用 乘法原理 计算出包含该元素的子数组数量 (right - i) * (i - left),这样就能计算出每个元素对题解的贡献

  • 84. 柱状图中最大的矩形 困难

本题和上一题的思路一样,都是将每个元素作为区间最大值时找区间范围,只不过本题计算的是最大面积而已

  • 85. 最大矩形 困难

本题需要转换成上一题来进行求解,可以看题解中的解题思路

2. 单调队列

单调队列是在单调栈的基础上实现了对 序列的两端操作,所以我们能够使用单调队列获取到 当前序列中的最值(即操作队首元素),来帮助我们解决 区间最值问题。此外 不要局限 在只使用一个单调队列解题,有的题目需要维护两个单调队列来分别记录区间内的最大值和最小值来求解。

理论上使用单调栈能解决的问题单调队列也能解决,不过在我们不需要获取区间最值时还是使用单调栈来求解。

能获取区间最大值(nums[deque.peekFirst()])的单调递减队列模板如下:

    Deque<Integer> deque = new ArrayDeque<>();
    for (int i = 0; i < nums.length; i++) {
        while (!deque.isEmpty() && nums[i] > nums[deque.peekLast()]) {
            int index = deque.pollLast();
            // 关于 index 的特殊处理
            process();
        }
        deque.addLast(i);
        
        // 必要处理逻辑
        process1();
    }
相关题目
  • 面试题59 - II. 队列的最大值 中等

  • 239. 滑动窗口最大值 困难

  • 1438. 绝对差不超过限制的最长连续子数组 中等

本题是单调队列和滑动窗口的经典应用题,需要使用两个单调队列分别来记录区间内的最大值和最小值,通过它们来判断绝对差是否符合题意,而滑动窗口则是想通过窗口的滑动来获取最长的子数组长度,在符合题意的情况下“猛猛地”往右滑动,不符合题意了再考虑将窗口缩小。

  • 654. 最大二叉树 中等

  • 2100. 适合打劫银行的日子 中等

这道题的题解中效率更好的是前缀和的解法,但是我觉得我不能一下就想出来使用前缀和求解,反而结合滑动窗口并维护两个单调队列更容易理解,大家各取所需吧,我把题解放上来供大家参考:

    public List<Integer> goodDaysToRobBank(int[] security, int time) {
        ArrayDeque<Integer> left = new ArrayDeque<>();
        ArrayDeque<Integer> right = new ArrayDeque<>();

        List<Integer> res = new ArrayList<>();
        int leftBegin = 0, leftEnd = 0;
        int rightBegin = time, rightEnd = time;
        while (rightEnd < security.length) {
            while (!left.isEmpty() && security[leftEnd] > security[left.peekLast()]) {
                left.pollLast();
            }
            left.addLast(leftEnd);
            while (!right.isEmpty() && security[rightEnd] < security[right.peekLast()]) {
                right.pollLast();
            }
            right.addLast(rightEnd);

            if (leftEnd - leftBegin == time) {
                if (left.size() == time + 1 && right.size() == time + 1) {
                    res.add(leftEnd);
                }
                if (leftBegin == left.peekFirst()) {
                    left.pollFirst();
                }
                leftBegin++;
                if (rightBegin == right.peekFirst()) {
                    right.pollFirst();
                }
                rightBegin++;
            }

            leftEnd++;
            rightEnd++;
        }

        return res;
    }

巨人的肩膀

  • 【宫水三叶】一题双解 :「单调栈」&「模拟」

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

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

相关文章

每日一题~组合总数III

原题链接&#xff1a;216. 组合总和 III - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a; 思路分析&#xff1a; 这是一个组合的问题&#xff0c;所以我们可以使用深度优先搜索&#xff08;DFS&#xff09;的方式将所有的情况都列举出来&#xff0c;然后将其中…

企业怎么申请OV SSL证书?

随着互联网的发展&#xff0c;越来越多的企业开始使用SSL证书来保护网站的安全。而OV SSL证书作为机构验证型的证书&#xff0c;能够重点强调网站安全、企业可信形象的网站&#xff0c;保密传输更加安全。那么&#xff0c;企业怎么申请OV SSL证书呢&#xff1f;接下来&#xff…

计算机视觉与深度学习-经典网络解析-GoogLeNet-[北邮鲁鹏]

这里写目录标题 GoogLeNet参考GoogLeNet模型结构创新点Inception结构&#xff0c;它能保留输入信号中的更多特征信息去掉了AlexNet的前两个全连接层&#xff0c;并采用了平均池化引入了辅助分类器 GoogLeNet GoogLeNet的设计主要特点是引入了Inception模块&#xff0c;这是一种…

聚观早报 | 杭州亚运开幕科技感拉满;腾讯官宣启动「青云计划」

【聚观365】9月25日消息 杭州亚运开幕科技感拉满 腾讯官宣启动「青云计划」 FF任命新全球CEO 比亚迪夺得多国销冠 iPhone 15/15 Pro销售低于预期 杭州亚运开幕科技感拉满 杭州第19届亚洲运动会开幕式23日晚在杭州奥体中心主体育馆举行&#xff0c;这届开幕式可谓科技感拉…

【linux进程(一)】深入理解进程概念--什么是进程?PCB的底层是什么?

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:Linux从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学更多操作系统知识   &#x1f51d;&#x1f51d; Linux进程 1. 前言2. PCB初认…

Java基本数据类型只有八种?第九种告诉你

一、前言 今天看了一下Oracle官网的JVM规范&#xff0c;发现基本数据不止有八种。 这个东西一直都是说着八种&#xff0c;官网把returnAddress放在八种基本数据类型一起说的&#xff0c;我们可以理解为第九种&#xff01; 常说的八种用于存储常见的数值和字符数据&#xff0…

计算机等级考试—信息安全三级真题六

目录 一、单选题 二、填空题 三、综合题 一、单选题

阿里云ESS弹性伸缩核心概念与基本使用

文章目录 1.ESS弹性伸缩基本概念1.1.弹性伸缩概念1.2.弹性伸缩应用场景1.3.弹性伸缩的应用模式 2.开通ESS弹性伸缩服务3.为KodCloud云盘集群创建弹性伸缩组3.1.创建伸缩组3.2.设置伸缩组的名称、类型、移除策略、健康检查3.3.设置组内实例数、冷却时间、网络类型、扩缩容策略、…

python循环判断

运算符短路逻辑python优先级 思维导图for循环 运算符 运算符含义and左边和右边同时为True,结果为Trueor左右两边其中一个为True,结果为Truenot如果操作数为True(False)&#xff0c;结果为False(True) 短路逻辑 从左到右&#xff0c;只有当第一操作数的值无法确定逻辑运算的结…

电脑开机慢怎么办?这样解决快速且有效!

“明明电脑用了才没多久&#xff0c;为什么开机那么慢呀&#xff1f;经常开个机需要好几分钟&#xff0c;这很耽误我的工作&#xff0c;请问有什么方法可以解决这个问题吗&#xff1f;” 电脑是现代生活不可或缺的工具&#xff0c;然而&#xff0c;当我们面对电脑开机缓慢的问题…

3D目标检测实战 | 图解KITTI数据集与数据格式

目录 1 数据集简介2 传感器坐标系3 数据集下载与组织4 数据内容说明4.1 矫正文件calib4.2 图像文件image4.3 点云文件velodyne4.4 标签文件label4.5 平面文件plane 1 数据集简介 KITTI数据集是一个广泛应用于自动驾驶和计算机视觉领域的公开数据集。该数据集由德国卡尔斯鲁厄理…

shell脚本自动化执行jar包

需要用shell脚本来自动化执行jar包&#xff0c;以后可以用jenkins来CI/CD&#xff0c;记录一下对应实现。 实现需求 以命令行执行shell传入的第一个参数为jar名进行执行。对应jar已存在执行进程&#xff0c;关闭对应进程后再执行。以后台方式执行对应的jar包&#xff0c;输出…

pymysql执行非查询语句会自动提交事务,关闭事务自动提交

一、前置条件 在mysql数据库生成数据&#xff1a; CREATE DATABASE mydatabase;CREATE TABLE Course (CourseID INT PRIMARY KEY,CourseName VARCHAR(100),Instructor VARCHAR(100),Credits INT,StudentID INT,FOREIGN KEY (StudentID) REFERENCES StudentInformation(Studen…

基于微信小程序四六级助手系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言用户微信小程序端的主要功能有&#xff1a;管理员的主要功能有&#xff1a;具体实现截图为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考论文参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W…

【C++prime plus中文第六版勘误记录】

P219-P220 这里对未进行初始化的指针pt进行了解引用的操作&#xff0c;这是不合理的&#xff0c;此问题在 stack overflow上也有人提出疑问&#xff0c;下面是问题与解答&#xff1a;

基于微信小程序的校园维修平台设计与实现(源码+lw+部署文档+讲解等

文章目录 前言学生微信小程序端的主要功能有&#xff1a;维修员微信小程序的主要功能有&#xff1a;管理员的主要功能有&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取…

Android 实现椭圆形中心发散渐变绘制

1. 前言 工作中有一个需求,需要实现一个椭圆形的中心发散渐变效果,并且可以自由指定椭圆比例,旋转角度等。 Android中实现椭圆形的绘制很简单,只需要调用canvas.drawOval()就可以了,圆形的中心发散渐变可以使用RadialGradient,但是椭圆形的中心发散渐变效果Android自带的…

Leetcode算法入门与数组丨6. 数组双指针、滑动窗口

文章目录 1 双指针基础知识1.1 双指针简介1.2 左右指针&#xff08;对撞指针&#xff09;1.3 快慢指针1.4 分离双指针 2 滑动窗口基础知识2.1 滑动窗口算法介绍2.2 滑动窗口适用范围2.3 固定长度滑动窗口2.4 不固定长度滑动窗口 1 双指针基础知识 1.1 双指针简介 双指针&…

LeetCode算法心得——美丽塔 I(HashMap)

大家好&#xff0c;我是晴天学长&#xff0c;hashmap的灵活应用&#xff0c;然后边界的细节处理&#xff0c;需要的小伙伴可以关注支持一下哦&#xff01;后续会继续更新的。 1) .美丽塔 美丽塔 I 给你一个长度为 n 下标从 0 开始的整数数组 maxHeights 。 你的任务是在坐标轴…

SpringBoot项目(百度AI整合)——如何在Springboot中使用文字识别OCR入门

前言 前言&#xff1a;本系列博客尝试结合官网案例&#xff0c;阐述百度 AI 开放平台里的组件使用方式&#xff0c;核心是如何在spring项目中快速上手应用。 本文介绍如何在Springboot中使用百度AI的文字识别OCR 其他相关的使用百度AI的文章列表如下&#xff1a; 如何在Spri…