当前位置: 首页 > news >正文

单调栈一文深度解析

单调栈一文深度解析

    • 一、单调栈基础概念
      • 1.1 定义与核心特性
      • 1.2 与普通栈的区别
      • 1.3 核心操作模板
    • 二、单调栈核心原理
      • 2.1 单调性维护机制
      • 2.2 典型应用场景
    • 三、经典案例解析
      • 下一个更大元素
        • 问题描述
        • 思路分析
        • 代码实现
        • 复杂度分析
      • 接雨水问题
        • 问题描述
        • 思路分析
        • 代码实现
        • 复杂度分析
      • 柱状图最大面积
        • 问题描述
        • 思路分析
        • 代码实现
        • 复杂度分析
    • 四、单调栈解题模板
      • 4.1 通用解题步骤
      • 4.2 常见问题映射表
    • 五、进阶技巧与优化
      • 5.1 双向单调栈
      • 5.2 单调栈与动态规划结合
      • 5.3 注意事项

单调栈是一种专门处理元素间序关系的高效数据结构,它通过维护栈内元素的单调性,能够在线性时间内解决诸如下一个更大/小元素系列经典问题。本文我将从单调栈的核心原理出发,结合经典案例,解析并总结其解题模板及优化技巧,帮你掌握这一高效解题工具。

一、单调栈基础概念

1.1 定义与核心特性

单调栈是指栈内元素(从栈底到栈顶)保持单调递增或单调递减的栈结构。根据单调性不同,分为:

  • 单调递增栈:栈内元素始终满足 stack[i] <= stack[i+1]
  • 单调递减栈:栈内元素始终满足 stack[i] >= stack[i+1]

其核心特性是:当新元素入栈时,通过弹出栈顶不满足单调性的元素,确保栈内元素始终保持单调。这种特性使得单调栈能够高效处理「元素与周边元素的序关系」问题。
单调递增栈

1.2 与普通栈的区别

特性普通栈单调栈
元素顺序无约束严格单调
主要操作push/pop维护单调性
典型场景表达式求值序关系问题
时间复杂度不定均摊O(n)

1.3 核心操作模板

// 单调递增栈模板
Stack<Integer> stack = new Stack<>();
for (int num : nums) {while (!stack.isEmpty() && stack.peek() >= num) { // 破坏递增性时弹出int poped = stack.pop();// 处理弹出元素}stack.push(num); // 新元素入栈,确保栈递增
}// 单调递减栈模板
Stack<Integer> stack = new Stack<>();
for (int num : nums) {while (!stack.isEmpty() && stack.peek() <= num) { // 破坏递减性时弹出int poped = stack.pop();// 处理弹出元素}stack.push(num); // 新元素入栈,确保栈递减
}

二、单调栈核心原理

2.1 单调性维护机制

  • 入栈规则:新元素入栈前,弹出所有破坏单调性的栈顶元素
  • 元素处理:弹出元素时,当前新元素即为其「下一个更大/更小元素」
  • 均摊复杂度:每个元素最多入栈/出栈一次,总时间复杂度O(n)
    单调递减栈维护

2.2 典型应用场景

  1. 下一个更大元素:数组中每个元素右侧第一个比它大的元素
  2. 接雨水问题:计算柱状图能接的雨水总量
  3. 柱状图最大面积:求解直方图中的最大矩形面积
  4. 每日温度:计算每天需要等待多久才会升温
  5. 股票买卖问题:寻找最佳买卖时机的变种问题

三、经典案例解析

下一个更大元素

问题描述

给定数组nums,返回一个新数组,其中每个元素是原数组对应位置元素的下一个更大元素(右侧第一个比它大的元素,不存在则为-1)。

思路分析
  • 使用单调递增栈,栈中保存尚未找到下一个更大元素的元素下标
  • 遍历数组时,新元素与栈顶元素比较:
    • 若新元素更大:栈顶元素的下一个更大元素即为当前元素,弹出并记录
    • 否则:新元素下标入栈
代码实现
public int[] nextGreaterElement(int[] nums) {int[] res = new int[nums.length];Stack<Integer> stack = new Stack<>(); // 存储下标,栈中元素对应的值单调递增for (int i = nums.length - 1; i >= 0; i--) {while (!stack.isEmpty() && nums[stack.peek()] <= nums[i]) {stack.pop(); // 弹出不满足递增的元素}res[i] = stack.isEmpty() ? -1 : nums[stack.peek()];stack.push(i);}return res;
}
复杂度分析
  • 时间复杂度:O(n),每个元素入栈出栈各一次
  • 空间复杂度:O(n),栈空间最多存储n个元素

接雨水问题

问题描述

给定n个非负整数表示柱状图的高度,计算下雨后能接多少雨水。

思路分析
  • 单调栈解法:维护单调递减栈,栈中存储柱子下标
  • 当新柱子高度 >= 栈顶柱子高度时,弹出栈顶,计算中间凹槽的接水量
  • 接水量由左右边界的较小值减去中间柱子高度决定
代码实现
public int trap(int[] height) {int res = 0;Stack<Integer> stack = new Stack<>(); // 存储下标,栈中高度单调递减for (int i = 0; i < height.length; i++) {while (!stack.isEmpty() && height[i] >= height[stack.peek()]) {int mid = stack.pop();if (stack.isEmpty()) break; // 没有左边界,无法形成凹槽int left = stack.peek();int width = i - left - 1;int h = Math.min(height[left], height[i]) - height[mid];res += width * h;}stack.push(i);}return res;
}
复杂度分析
  • 时间复杂度:O(n),每个元素入栈出栈各一次
  • 空间复杂度:O(n),栈空间最多存储n个元素

柱状图最大面积

问题描述

给定直方图的高度数组,求其中最大矩形面积。

思路分析
  • 单调递增栈:栈中存储下标,对应高度单调递增
  • 每个元素出栈时,视为矩形的高,宽度由左右边界决定:
    • 左边界:栈顶元素(弹出后的新栈顶)
    • 右边界:当前元素下标
    • 宽度 = 右边界 - 左边界 - 1
代码实现
public int largestRectangleArea(int[] heights) {int res = 0;Stack<Integer> stack = new Stack<>(); // 存储下标,栈中高度单调递增int[] newHeights = Arrays.copyOf(heights, heights.length + 2); // 首尾加0,避免边界处理for (int i = 0; i < newHeights.length; i++) {while (!stack.isEmpty() && newHeights[i] < newHeights[stack.peek()]) {int h = newHeights[stack.pop()];int w = i - stack.peek() - 1;res = Math.max(res, h * w);}stack.push(i);}return res;
}
复杂度分析
  • 时间复杂度:O(n),每个元素入栈出栈各一次
  • 空间复杂度:O(n),栈空间及辅助数组

四、单调栈解题模板

4.1 通用解题步骤

  1. 确定单调性:根据问题确定使用递增栈还是递减栈(找更大元素用递增栈,找更小元素用递减栈)
  2. 定义栈存储内容:通常存储元素下标(便于计算距离),而非直接存储元素值
  3. 处理边界条件:通过虚拟边界(如首尾加0)简化边界判断
  4. 元素弹出逻辑:当新元素破坏单调性时,弹出栈顶元素并计算相关结果

4.2 常见问题映射表

问题类型单调性栈存储弹出条件结果计算方式
下一个更大元素递增栈下标新元素 > 栈顶元素新元素为栈顶元素的下一个更大元素
柱状图最大面积递增栈下标新元素 < 栈顶元素栈顶元素为高,左右边界算宽度
接雨水递减栈下标新元素 >= 栈顶元素栈顶元素为中间柱,算凹槽水量
每日温度递增栈下标新元素温度 > 栈顶温度天数为下标差

五、进阶技巧与优化

5.1 双向单调栈

对于需要同时考虑左右边界的问题(如接雨水问题),可分别使用左右单调栈计算每个位置的左右最大高度,再取较小值计算水量。虽然时间复杂度同为O(n),但代码逻辑更清晰:

// 接雨水双向栈解法
public int trap(int[] height) {int n = height.length;int[] leftMax = new int[n];int[] rightMax = new int[n];// 左单调栈(递增)Stack<Integer> leftStack = new Stack<>();for (int i = 0; i < n; i++) {while (!leftStack.isEmpty() && height[leftStack.peek()] <= height[i]) {leftStack.pop();}leftMax[i] = leftStack.isEmpty() ? 0 : height[leftStack.peek()];leftStack.push(i);}// 右单调栈(递增)Stack<Integer> rightStack = new Stack<>();for (int i = n-1; i >= 0; i--) {while (!rightStack.isEmpty() && height[rightStack.peek()] <= height[i]) {rightStack.pop();}rightMax[i] = rightStack.isEmpty() ? 0 : height[rightStack.peek()];rightStack.push(i);}int res = 0;for (int i = 0; i < n; i++) {res += (Math.min(leftMax[i], rightMax[i]) - height[i]);}return res;
}

5.2 单调栈与动态规划结合

在「股票买卖的最佳时机」系列问题中,可结合单调栈优化动态规划的状态转移,将O(n²)的暴力解法优化至O(n),我将在之后的博客中进行讲解,持续关注不错过哦。

5.3 注意事项

  • 下标与值的区分:栈中存储下标而非值,便于计算元素间的距离(如宽度、天数差)
  • 虚拟元素处理:通过在数组首尾添加虚拟元素(如0),避免单独处理边界条件
  • 单调性方向判断:明确问题需要「更大」还是「更小」元素,选择对应的单调栈类型

单调栈通过维护元素的单调性,将原本需要双重循环的序关系问题优化至线性时间,是解决「下一个更大/更小元素」类问题的核心工具。其核心思想是:用栈结构缓存尚未处理的元素,通过单调性约束快速确定元素间的序关系

That’s all, thanks for reading!
觉得有用就点个赞、收进收藏夹吧!关注我,获取更多干货~

http://www.lqws.cn/news/556831.html

相关文章:

  • NLP——文本预处理(下)
  • 翻译服务器
  • Redis高级数据结构深度解析:BitMap、布隆过滤器、HyperLogLog与Geo应用实践
  • 趣味数据结构之——数组
  • Java 使用 Easy Excel 进行 Excel 数据导入导出
  • 一分钟了解思路链提示词(Chain-of-thought Prompting)
  • uni-app manifest.json 配置:定制化应用的各项功能和行为
  • 基于Pandas和FineBI的昆明职位数据分析与可视化实现(二)- 职位数据清洗与预处理
  • 《自动控制原理 》- 第 1 章 自动控制的基本原理与方式
  • Linux基本指令篇 —— more指令
  • PostgreSQL 中,若需显示 不在 `IN` 子句列表中的数据
  • SQL常用命令
  • 阿里云Ubuntu服务器上安装MySQL并配置远程连接
  • 网络缓冲区
  • Solidity学习 - 错误处理
  • ffpaly播放 g711a音频命令
  • 【学习笔记】深入理解Java虚拟机学习笔记——第12章 Java内存模型与线程
  • 设计模式之抽象工厂模式
  • Docker 入门教程(五):Docker 命令思维导图
  • 【分布式机架感知】分布式机架感知能力的主流存储系统与数据库软件
  • 微处理原理与应用篇---STM32寄存器控制GPIO
  • 矩阵的条件数(Condition Number of a Matrix)
  • 华为云Flexus+DeepSeek征文 | 基于华为云ModelArts Studio安装NoteGen AI笔记应用程序
  • Learning PostgresSQL读书笔记: 第11章 Transactions, MVCC, WALs, and Checkpoints
  • 基于Docker的mosquitto安装测试
  • FPGA设计的上板调试
  • python多线程详细讲解
  • Python爬虫实战:研究difflib库相关技术
  • Ubuntu 主机通过 `enp4s0` 向开发板共享网络的完整步骤
  • 默克树技术原理