数据结构 - 单调栈 Monotonic Stack

news/2025/2/24 15:10:07/文章来源:https://www.cnblogs.com/wenbinteng/p/18734108

在算法问题中,许多问题都与单调性相关。例如:

  • 寻找数组中某个元素左侧或右侧比它大的元素;
  • 解决栈相关的问题,比如计算有效的括号、接雨水问题;
  • 处理与区间、滑动窗口相关的动态问题。

许多这样的问题如果直接暴力处理,可能会导致 \(O(n^2)\) 的复杂度,这在面对大数据时会变得非常低效。为了提高效率,我们可以使用一种叫做单调栈(Monotonic Stack)的数据结构,它能将许多问题的复杂度优化到 \(O(n)\),并且简单高效。

本文将介绍单调栈的基本概念、操作方法、应用场景以及常见题目,帮助你在遇到类似问题时能够游刃有余。

1. 什么是单调栈

单调栈是一个结构,它的元素满足一定的单调性(递增或递减)。简单来说,单调栈的核心思想是:

  • 单调递增栈: 栈内的元素按递增的顺序排列,栈顶元素是当前栈中最小的元素。
  • 单调递减栈: 栈内的元素按递减的顺序排列,栈顶元素是当前栈中最大的元素。

通过维护这种单调性,我们可以高效地解决许多和“下一个更大元素”或“下一个更小元素”相关的问题。

2. 单调栈的基本操作

单调栈的入栈与出栈操作需要根据当前元素与栈顶元素的大小关系来决定是否出栈,直到栈内元素符合单调性的需求。

  • 单调递增栈:单调递增栈确保栈内元素从底到顶是递增的。当遇到一个新的元素时,栈顶比当前元素小的元素会出栈,直到栈顶元素大于当前元素为止。这样栈中始终保持递增的顺序。

    std::stack<int> s;
    void insert(int x) {while (!s.empty() && s.top() < x)s.pop();s.push(x);
    }
    
  • 单调递减栈:单调递减栈则相反,栈内元素保持递减顺序。当遇到一个新的元素时,栈顶比当前元素大的元素会出栈,直到栈顶元素小于当前元素为止。这样栈中始终保持递减的顺序。

3. 单调栈的复杂度分析

  • 时间复杂度:遍历 \(n\) 个元素的数组时,每个元素至多入栈一次和出栈一次,因此处理数组的时间复杂度为 \(O(n)\)
  • 空间复杂度:\(O(n)\)

4. 单调栈的应用

4.1 下一个更大的元素

给定一个数组 arr,要求找出每个元素右边的第一个比它大的元素。若没有这样的元素,则输出 -1。

解决思路:

  • 使用单调递减栈,从右到左遍历数组。
  • 当栈顶元素小于当前元素时,栈顶元素出栈,直到栈顶元素大于当前元素或栈为空。
  • 当前元素的下一个更大元素即为栈顶元素(如果栈为空则为 -1)。
std::vector<int> nextGreaterElement(std::vector<int>& arr) {int n = arr.size();std::vector<int> result(n, -1);std::stack<int> s;for (int i = n - 1; i >= 0; i--) {while (!s.empty() && s.top() <= arr[i]) {s.pop();}if (!s.empty()) {result[i] = s.top();}s.push(arr[i]);}return result;
}

4.2 接雨水问题

给定一个非负整数数组 height,每个元素代表一个柱子的高度。求在这些柱子之间可以接多少雨水。

解题思路:

每个位置上的水量是由其左侧和右侧的最大高度决定的,具体计算可以通过单调栈来实现。

  • 使用单调递减栈,从左往右遍历数组,计算每个元素的接雨水量。
  • 对于每个元素,栈顶元素之前的所有柱子都可能作为容器的边界,当遇到一个更高的柱子时,就可以计算这个区间的水量。
int trap(std::vector<int>& height) {int n = height.size();int water = 0;std::stack<int> s;for (int i = 0; i < n; ++i) {while (!s.empty() && height[i] > height[s.top()]) {int top = s.top();s.pop();if (st.empty())break;int distance = i - s.top() - 1;int bounded_height = std::min(height[i], height[s.top()]) - height[top];water += distance * bounded_height;}st.push(i);}return water;
}

5. 实例

42. 接雨水 - 力扣(LeetCode)

84. 柱状图中最大的矩形 - 力扣(LeetCode)

85. 最大矩形 - 力扣(LeetCode)

496. 下一个更大元素 I - 力扣(LeetCode)

503. 下一个更大元素 II - 力扣(LeetCode)

739. 每日温度 - 力扣(LeetCode)

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Flink 实战之维表关联

生产应用中,经常遇到将实时 **流式数据** 与 **维表数据** 进行关联的场景。常见的维表关联方式有多种,本文对以下 3 种进行了实现,并对每种方法的优缺点进行了比较:1. 预加载维表 2. 异步 IO 3. 广播维表下面分别使用不同方式来完成维表 join 的实验,附源码和实时动效。F…

Sa2VA环境搭建推理测试

引子 Sa2VA模型通过结合SAM-2和LLaVA,将文本、图像和视频统一到共享的LLM标记空间中,能够在少量指令微调下执行多种任务,如图像/视频对话、指称分割和字幕生成。该模型在视频编辑和内容创作中展现出强大的性能,在相关基准任务中达到了SOTA水平。OK,那就让我们开始吧。一、…

20-bluecms代码审计、thinkphp相关知识cve和cnvd编号申请

1、对bluecms进行代码审计,分析复现文件上传、ssti模板注入、文件删除等漏洞 文件上传审计admin/tpl_manage.php 文件发现,在do_edit模块有三个参数(act = do_edit、tpl_name = 写入文件名称、tpl_content = 写入内容,且代码中未对文件名过滤,导致可以上传任意文件。查看对…

ios SDK AB 开关切换

在数据库的这个服务器 然后再ctest1数据库新建编辑器然后查询select* fromapp_config ac whereaccess_no = 12100186 //这个是应用IDand module = abSwitchand param_name = export_otel_ab查到后,把param_value改为B,或者A,然后回车,然后点击图中的保存 保存后等两分钟,…

CS Course Learning

【李宏毅】2024大语言模型课程 课程学习课程链接:https://speech.ee.ntu.edu.tw/~hylee/genai/2024-spring.php Bilibili相关视频链接:https://www.bilibili.com/video/BV1XS411w7qrGPT: Autoregressive model In-context LearningChain of Thoughts (CoT) Tree of Thoughts …

跟着狂神学markdown作业01天

markdown学习 标题 一共可以做六级标题 格式为#+空格+标题 几级标题就打几个空格 字体 粗体:hello,world 两边各加两个*号 斜体:hello,world 两边各加一个*号 粗体+斜体:hello,world 两边各加三个***号 删除效果:hello,world 引用选择狂神说java,走向人生巅峰(用>…

java知识面试day4

1.常见的关键字有哪些static:静态变量,静态变量被所有对象共享,在内存中只有一个副本。具有静态变量,静态方法块,静态代码块(在类加载时候被指执行一次),静态内部类:非静态内部类需要依赖外部实列,但静态内部类不需要。final 基本数据类型用final修饰不能修改,引用对象被…

[QOJ 8366] 火车旅行

毒瘤边化点,有人说非排列只需要加一些细节,但是这个题毒瘤在于非排列。 statement 给定一个长度为 \(n\) 的序列 \(a_i\)。 对于位置 \(x\) 和 \(y\):若 \(y < x\) 且 \(max_{y < i < x} a_i < min(a_x, a_y)\) 则位于 \(x\) 的棋子可以花费 \(L_x\) 的代价跳到…

uipath更新到最新版本2025.0.161出现严重问题

uipath更新到最新版本2025.0.161出现严重问题:1. 打开既有项目,会报CS0246错误2. 无法创建新项目,一直报无权限访问尝试办法:1. 重新安装uipath,未解决2. 删除项目重新添加,未解决3. 给账户添加最高权限,未解决 workaround:把项目从默认文件夹复制到其他盘(除了C盘外…

Python正则表达式之re.compile函数

​在Python编程语言中,re.compile函数是正则表达式模块(re)中的一个核心组件,它负责将文本形式的正则表达式编译成一个正则表达式对象。这个对象随后可以被用来执行高效的模式匹配操作,如查找、替换或者分割字符串等。理解并有效利用 re.compile对于编写高效且可维护的正则表…

Unity Addresable打包总结第二弹

前言 前文介绍了Addressables在本地打包是怎么使用,这里介绍下怎么打远程包,并且怎么做到打增量包,Lets Go! 远程包新建一个Group,将它的 BUild & Load Paths 改为Remote,并将RemoteRes资源文件夹塞入Remote Group,其中包含一个Capsule.prefab资源:在Addressabvles …

BUUCTF-RE-[2019红帽杯]easyRE

这道题很难,但是并不难在他的解题要用到的方法和技巧上,而是难在它的题目设计。做的过程中真的有一种闯关的感觉,非常有趣 首先我们通过对字符的定位我们可以来到sub_4009C6函数 __int64 sub_4009C6() {__int64 result; // raxint i; // [rsp+Ch] [rbp-114h]__int64 v2; // …