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

【LeetCode 热题 100】84. 柱状图中最大的矩形——(解法一)单调栈+三次遍历

Problem: 84. 柱状图中最大的矩形
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。

文章目录

  • 整体思路
  • 完整代码
  • 时空复杂度
    • 时间复杂度:O(N)
    • 空间复杂度:O(N)

整体思路

这段代码旨在解决一个经典的、难度较高的问题:柱状图中最大的矩形 (Largest Rectangle in Histogram)。问题要求在一个由非负整数组成的数组(代表柱状图)中,找出能勾勒出的最大矩形的面积。

该算法采用了一种非常高效且标准的 单调栈 (Monotonic Stack) 解法。其核心思想是,对于每一个柱子 heights[i],如果我们能确定以它为最低高度的那个最大矩形的左右边界,我们就能计算出这个矩形的面积。全局的最大面积,必然是这些以每个柱子为最低高度的矩形面积中的最大值。

算法的整体思路可以分解为以下三个步骤:

  1. 第一步:为每个柱子找到其左侧第一个更矮的柱子

    • 算法使用一个单调栈(从栈底到栈顶,索引对应的柱子高度是单调递增的),并从左到右遍历 heights 数组。
    • 对于当前柱子 heights[i],它会不断地从栈顶弹出那些比它高或等高的柱子。
    • 当这个过程结束后,栈顶的柱子(如果存在)就是 i 左侧的第一个严格比 heights[i] 矮的柱子。这个柱子的索引被记录在 left[i] 中。如果栈为空,说明左侧没有比它更矮的柱子,我们将边界记为一个虚拟索引 -1
  2. 第二步:为每个柱子找到其右侧第一个更矮的柱子

    • 这个过程与第一步完全对称。
    • 算法清空单调栈,然后从右到左遍历 heights 数组。
    • 同样,对于当前柱子 heights[i],它会找到其右侧第一个严格更矮的柱子,并将其索引记录在 right[i] 中。如果右侧没有更矮的,边界记为虚拟索引 n(数组长度)。
  3. 第三步:计算最终结果

    • 现在,对于每一个柱子 i,我们已经知道了以它为最低高度的矩形的左右边界(分别是 left[i]right[i])。
    • 这个矩形的宽度可以被计算出来,即 width = right[i] - left[i] - 1
    • 其面积就是 area = heights[i] * width
    • 最后,算法遍历所有柱子,计算出以每个柱子为最低高度的矩形面积,并取其中的最大值作为最终答案。

这种方法通过两次线性扫描和单调栈,高效地为每个柱子找到了其作为矩形高度时的最大可能宽度,从而在线性时间内解决了问题。

完整代码

class Solution {/*** 计算给定柱状图中最大矩形的面积。* @param heights 代表柱状图中各个柱子高度的数组* @return 最大矩形的面积*/public int largestRectangleArea(int[] heights) {int n = heights.length;// st: 一个单调栈,用于存储柱子的索引。Deque<Integer> st = new ArrayDeque<>();// --- 步骤 1: 查找每个柱子左边第一个更矮的柱子 ---// left[i] 存储 heights[i] 左边第一个比它矮的柱子的索引int[] left = new int[n];for (int i = 0; i < n; i++) {int h = heights[i];// 维护单调递增栈:当栈不为空且栈顶柱子 >= 当前柱子时,弹出栈顶。while (!st.isEmpty() && h <= heights[st.peek()]) {st.pop();}// 此时栈顶即为左侧第一个更矮的柱子。如果栈为空,说明左侧没有更矮的。left[i] = st.isEmpty() ? -1 : st.peek();// 将当前柱子索引入栈st.push(i);}// --- 步骤 2: 查找每个柱子右边第一个更矮的柱子 ---// 清空栈以备复用st.clear();// right[i] 存储 heights[i] 右边第一个比它矮的柱子的索引int[] right = new int[n];for (int i = n - 1; i >= 0; i--) {int h = heights[i];// 同样维护单调递增栈(相对于遍历方向)while (!st.isEmpty() && h <= heights[st.peek()]) {st.pop();}// 此时栈顶即为右侧第一个更矮的柱子。如果栈为空,说明右侧没有更矮的。right[i] = st.isEmpty() ? n : st.peek();// 将当前柱子索引入栈st.push(i);}// --- 步骤 3: 计算最大面积 ---int ans = 0;for (int i = 0; i < n; i++) {// 对于每个柱子i,以它为最低高度的矩形面积为:// 高度: heights[i]// 宽度: 右边界 - 左边界 - 1,即 (right[i] - left[i] - 1)ans = Math.max(ans, heights[i] * (right[i] - left[i] - 1));}return ans;}
}

时空复杂度

时间复杂度:O(N)

  1. 第一遍扫描 (计算 left 数组):外层 for 循环遍历 N 次。内层的 while 循环虽然看起来可能导致复杂度升高,但由于每个元素的索引最多只会入栈一次、出栈一次,所以所有 pushpop 操作在整个循环中的总次数是 O(N)。因此,这部分的均摊时间复杂度为 O(N)
  2. 第二遍扫描 (计算 right 数组):与第一遍扫描的逻辑和分析完全相同,其时间复杂度也是 O(N)
  3. 第三遍扫描 (计算 ans):这是一个简单的 for 循环,遍历 N 次,每次执行 O(1) 的操作。时间复杂度为 O(N)

综合分析
算法的总时间复杂度是三次独立的线性扫描的和:O(N) + O(N) + O(N) = O(N)

空间复杂度:O(N)

  1. 主要存储开销:算法使用了两个辅助数组 leftright,以及一个栈 st
    • int[] left = new int[n]: 占用了 O(N) 的空间。
    • int[] right = new int[n]: 占用了 O(N) 的空间。
    • Deque<Integer> st: 在最坏的情况下(例如,一个严格递增的 heights 数组),栈需要存储所有 N 个索引。因此,栈的空间复杂度为 O(N)。
  2. 综合分析
    算法所需的额外辅助空间由 left 数组、right 数组和栈共同构成。总的空间复杂度为 O(N) + O(N) + O(N) = O(N)

参考灵神

http://www.lryc.cn/news/608958.html

相关文章:

  • 二叉树的锯齿形层次遍历
  • 9.苹果ios逆向-FridaHook-ios中的算法(CCCrypt)
  • CCF-GESP 等级考试 2025年6月认证C++一级真题解析
  • wordpress登陆前登陆后显示不同的顶部菜单
  • 最简单的零基础软件测试学习路线
  • Libevent(5)之使用教程(4)工具
  • k8s黑马教程笔记
  • 快速搭建一个非生产k8s环境
  • 【运维基础】Linux 硬盘分区管理
  • k8s+isulad 国产化技术栈云原生技术栈搭建4-添加worker节点
  • Hyper-V + Centos stream 9 搭建K8s集群(二)
  • k8s+isulad 国产化技术栈云原生技术栈搭建3-master节点安装
  • [硬件电路-148]:数字电路 - 什么是CMOS电平、TTL电平?还有哪些其他电平标准?发展历史?
  • Go语言实战案例:TCP服务器与客户端通信
  • 案例介绍|JSON数据格式的转换|pyecharts模块简介
  • Kafka——怎么重设消费者组位移?
  • 构建企业级Web应用:AWS全栈架构深度解析
  • AtCoder Beginner Contest 417
  • [硬件电路-147]:模拟电路 - DC/DC电压的三种架构:升压(Boost)、降压(Buck)或升降压(Buck-Boost)
  • 跨语言模型中的翻译任务:XLM-RoBERTa在翻译任务中的应用
  • 界面规范4-按钮
  • IntelliJ IDEA开发编辑器摸鱼看股票数据
  • Parcel 使用详解:零配置的前端打包工具
  • 关于车位引导及汽车乘梯解决方案的专业性、系统性、可落地性强的综合设计方案与技术实现说明,旨在为现代智慧停车楼提供高效、安全、智能的停车体验。
  • electron-多线程
  • 嵌入式——数据结构:单向链表的函数创建
  • 常见的深度学习模块/操作中的维度约定(系统性总结)
  • Docker-03.快速入门-部署MySQL
  • 介绍JAVA语言、介绍greenfoot 工具
  • 北邮:LLM强化学习架构Graph-R1