Leetcode 柱状图中最大的矩形

在这里插入图片描述

h 是右边界,连续多个高度递增的柱子,如果遇到下一个 h < 栈顶元素(是最大的元素,单调递增栈),那么会不断出栈来更新计算最大面积。

并非是一次性计算出最大面积的,很重要的一点是while (!stack.isEmpty()这一部分的判断条件,会持续多次计算并更新最大面积,

你提到的情况是非常重要的,并且确实在某些情况下会让人觉得应该以栈底元素为高度,但实际上这需要从栈的完整处理流程来理解。我们仍然应该按照栈顶元素作为当前高度来计算矩形面积,而栈底元素最终也会被弹出并正确处理。

让我们详细分析这个具体输入 [5, 6, 7, 2] 的执行过程,看看为何最终仍会正确地处理到以栈底元素 5 为高度的矩形。

步骤解析:

初始化:
  • 我们的输入数组是 [5, 6, 7, 2],依然使用单调栈来计算最大矩形面积。
  • 遍历每个柱子,同时维护一个递增栈(存放柱子的索引)。
遍历开始:
  1. i = 0, heights[0] = 5

    • 栈为空,将 0 压入栈,栈为 [0]
  2. i = 1, heights[1] = 6

    • 栈顶元素对应的高度是 56 > 5,继续保持递增顺序,将 1 压入栈,栈为 [0, 1]
  3. i = 2, heights[2] = 7

    • 栈顶元素对应的高度是 67 > 6,继续递增,将 2 压入栈,栈为 [0, 1, 2]
  4. i = 3, heights[3] = 2

    • 现在,当前柱子 2 < 7,不再递增了,我们需要弹出栈顶来计算面积。
弹栈计算过程:
  1. 弹出 2 索引的柱子 (高度 7)

    • 栈顶的高度是 7,宽度为 1(因为 i - stack.peek() - 1 = 3 - 1 - 1 = 1),即矩形面积为 7 * 1 = 7
    • 更新最大面积为 7
  2. 弹出 1 索引的柱子 (高度 6)

    • 栈顶的高度是 6,宽度为 2(因为 i - stack.peek() - 1 = 3 - 0 - 1 = 2),即矩形面积为 6 * 2 = 12
    • 更新最大面积为 12
  3. 弹出 0 索引的柱子 (高度 5)

    • 栈顶的高度是 5,宽度为 3(因为栈已为空,所以宽度就是 i = 3),即矩形面积为 5 * 3 = 15
    • 更新最大面积为 15
继续遍历:
  1. 压入当前柱子 2
    • 现在将索引 3 压入栈,栈为 [3]
遍历结束:
  1. 遍历结束后,栈中还有元素 3,对应的高度是 2,我们需要处理剩余的栈:
    • 以高度 2 作为最后的矩形,高度为 2,宽度为 4(因为栈为空,宽度就是整个数组的长度 n = 4),即矩形面积为 2 * 4 = 8

最终结果:

最大面积是 15,对应的就是以高度 5 的矩形。

总结:

  1. [5, 6, 7, 2] 的案例中,当遇到比栈顶更小的 2 时,栈中的元素会依次被弹出,首先以 7 作为高度计算矩形面积,接着以 6,最后以 5。栈底元素 5 也会在这个过程中被正确地弹出,并计算出它作为高度的最大矩形面积,因此最终以栈底元素 5 为高度的矩形仍然会被正确计算

  2. 之所以使用栈顶元素作为 height,是因为栈顶元素对应的柱子是当前需要被“封闭”的柱子,当前柱子限制了栈顶柱子的右边界,所以我们需要计算以它为高度的最大矩形。

  3. 栈底元素最终也会被弹出并处理,只不过它的处理顺序是等到它的右边界被确定之后才会弹出计算,确保不会漏掉任何可能形成的更大矩形。

java solution

class Solution {
    public int largestRectangleArea(int[] heights) {
        int n = heights.length;
        int maxArea = 0;
        //栈中存放的是柱子的下标,而不是高度值
        Stack<Integer> stack = new Stack<>();
        for(int i = 0; i <= n; ++i) {
            //h是当前柱子的高度,当i == n 时意味着到达了我们设置的最右边界
            int h = (i == n) ? 0 : heights[i];
            //stack中的下标对应的元素值是递增的,栈顶元素作为下标所对应的元素是之前连续递增高度的最大值,
            //而h小于栈顶元素下标对应高度值,意味着碰到了右边界,需要我们持续出栈来多次更新计算最大面积值
            while(!stack.isEmpty() && h < heights[stack.peek()]) {
                //确定高度和宽度
                int height = heights[stack.pop()];
                int width = (stack.isEmpty()) ? i : i - stack.peek() - 1;
                maxArea = Math.max(maxArea, height * width);
            }
            stack.push(i);
        }
        return maxArea;
    }
}

关于宽度的确定

int width = stack.isEmpty() ? i : i - stack.peek() - 1;

这行代码是用于计算当前弹出的栈顶元素形成的矩形的 宽度。它看起来有点复杂,但其实逻辑很清楚:我们要确定以弹出柱子的高度为基础的矩形,它的左右边界分别是什么。

代码片段:

int width = stack.isEmpty() ? i : i - stack.peek() - 1;

这行代码的作用是计算矩形的宽度,决定矩形的左右边界。我们可以将其分为两个部分来理解。

1. 为什么需要计算宽度?

当我们从栈中弹出一个柱子时,这个柱子的高度成为当前矩形的高度,但我们还需要知道矩形的左右边界才能计算面积。

  • 右边界:当前柱子 i 的索引可以看作是右边界,因为当我们弹出栈顶元素时,当前遍历到的柱子高度 h 比栈顶柱子矮(h < heights[stack.peek()]),因此它限制了栈顶柱子的扩展。
  • 左边界:左边界取决于栈中剩下的下一个元素(也就是当前弹出的柱子左边比它更低的柱子)。如果栈中已经没有元素了,说明弹出的这个柱子可以从索引 0 开始扩展到当前索引 i - 1

2. 代码解释

int width = stack.isEmpty() ? i : i - stack.peek() - 1;

这行代码是用来计算弹出的栈顶柱子对应的矩形宽度的,有两种情况:

情况 1:栈为空(stack.isEmpty()
  • 说明:当我们弹出栈顶元素后,栈为空,意味着弹出的柱子左边没有任何柱子阻碍它的扩展。因此,弹出的这个柱子可以从索引 0 一直延伸到当前柱子的索引 i - 1
  • 宽度:在这种情况下,矩形的宽度就是从 0i - 1,即宽度为 i。所以 width = i
  • 举例:假设栈中只有一个柱子 heights[0] = 5,遍历到 i = 3,此时弹出栈顶,栈变为空,矩形宽度是 3,因为它可以从索引 0 扩展到索引 2,即 width = 3
情况 2:栈不为空(!stack.isEmpty()
  • 说明:当弹出栈顶元素后,栈中还有其他柱子,这意味着弹出的柱子左边有另一个较低的柱子阻碍它扩展。此时,弹出栈顶柱子的矩形的左边界由下一个栈顶元素(stack.peek())决定。
  • 宽度计算:矩形的宽度是从下一个栈顶元素的索引 stack.peek() 加 1,到当前柱子之前的索引 i - 1。因此,宽度为 i - stack.peek() - 1
  • 举例:假设栈中有两个柱子 heights[0] = 5heights[1] = 6,遍历到 i = 3,此时弹出 heights[1],栈中剩下 heights[0]。弹出 heights[1] 后,宽度是从 stack.peek() 位置(即 0)加 1 到当前索引 i - 1,即 3 - 0 - 1 = 2

当栈不为空时,我们的目标是计算当前弹出栈顶元素所能形成的矩形宽度,它的左右边界分别是:

  • 右边界:当前遍历到的柱子之前的索引,也就是 i - 1
  • 左边界:栈中下一个元素的索引 stack.peek(),这个元素是弹出栈顶元素左边的柱子,它限制了弹出柱子的扩展,所以左边界应是 stack.peek() + 1

因此,宽度的计算可以表示为:

width = (i - 1) - (stack.peek() + 1) + 1

简化后就是:

width = i - stack.peek() - 1

这个公式简洁地表达了矩形的宽度,涵盖了从左边界(stack.peek() 后的那一个柱子)到右边界(当前柱子的前一个位置)的距离。

你已经掌握了宽度计算的关键逻辑,非常棒!

3. 具体示例

我们用 [5, 6, 7, 2] 这个例子来展示如何计算宽度。

  • 初始状态:遍历 5, 6, 7 时,栈依次存入 [0, 1, 2]

  • 遇到 22 < 7,开始弹出栈顶元素。

    1. 弹出 7(索引 2)

      • 栈中剩下 [0, 1]7 的右边界是当前索引 3,左边界是 6 的位置(stack.peek()1)。
      • 宽度为 3 - 1 - 1 = 1,矩形面积为 7 * 1 = 7
    2. 弹出 6(索引 1)

      • 栈中剩下 [0]6 的右边界仍然是当前索引 3,左边界是 5 的位置(stack.peek()0)。
      • 宽度为 3 - 0 - 1 = 2,矩形面积为 6 * 2 = 12
    3. 弹出 5(索引 0)

      • 栈为空,所以 5 的右边界是当前索引 3,而左边界是 0
      • 宽度为 3(因为栈为空),矩形面积为 5 * 3 = 15

4. 总结

  • 宽度的计算逻辑
    • 如果栈为空,说明弹出的柱子可以扩展到最左端,即从 0 到当前索引的前一个位置,宽度为 i
    • 如果栈不为空,说明弹出的柱子左边有其他柱子阻碍,它的左边界由下一个栈顶元素的索引决定,宽度为 i - stack.peek() - 1

通过这行代码,我们可以精确地计算每个弹出栈顶柱子所能形成的最大矩形的宽度,并结合高度一起更新矩形面积。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vivado自定义IP修改顶层后Port and Interface不更新解决方案

问题描述 在整个项目工程中&#xff0c;对自定义IP进行一个比较大的改动&#xff0c;新增了不少端口(这里具体的就是bram的读写端口)&#xff0c;修改是在block design中右击IP编辑在IP编辑工程中进行的。 在修改完所有代码后&#xff08;顶层新增了需要新加的输入输出端口&…

【计算机网络 - 基础问题】每日 3 题(四十九)

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?typeblog &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞…

字节流写入文件

一、创建输出流对象表示的文件三种方式 方法一&#xff1a; FileOutputStream fos new FileOutputStream("fos.txt",true);//最简便方法二&#xff1a; FileOutputStream fos new FileOutputStream(new File("fos.txt"));方法三&#xff1b; File f ne…

HCIP-HarmonyOS Application Developer 习题(十四)

&#xff08;多选&#xff09;1、HarmonyOs为应用提供丰富的Al(Artificial Intelligence)能力&#xff0c;支持开箱即用。下列哪些是它拥有的AI能力? A、通用文字识别 B、词性标注 C、实体识别 D、语音播报 答案&#xff1a;ABCD 分析&#xff1a; AI能力简介二维码生成根据开…

软考高级系统分析师,快背,都是精华知识点!

19、需求变更控制 需求变更控制过程&#xff1a; &#xff08;1&#xff09;变更申请。应记录变更的提出人、日期、申请变更的内容等信息。 &#xff08;2&#xff09;变更评估。对变更的影响范围、严重程度、经济和技术可行性进行系统分析。 &#xff08;3&#xff09;变更…

qt/c++中成员函数返回成员变量并且可以赋值

#创作灵感 最近在做仪表项目&#xff0c;由于客户提供的仪表故障指示灯只有10个固定位置&#xff0c;而故障指示灯却有80多个。为了解决这个问题&#xff0c;进过我的设计&#xff0c;项目中需要返回类的成员变量。并且还可以赋值给它。于是就产生了下面的代码。 class Foo { …

基于Multisim三极管B放大系数放大倍数测量电路设计(含仿真和报告)

【全套资料.zip】三极管B放大系数放大倍数测量电路电路设计Multisim仿真设计数字电子技术 文章目录 功能一、Multisim仿真源文件二、原理文档报告资料下载【Multisim仿真报告讲解视频.zip】 功能 1.用三个数码管显示B的大小&#xff0c;分别显示个位、十位和百位。 2.显示范围…

FineReport 分页

按组分页 按组分页就是让数据按组来进行分页显示&#xff0c;每个组的数据占据一页。 例如报表原本是按照纸张大小进行分页的&#xff0c;现在希望能够按照货主地区进行分页&#xff0c;一个地区的数据显示在同一个页面当中。 在每组数据前设置「行前分页」或者在每组数据后…

健身房管理系统设计与实现(源码+定制+讲解)健身房预约系统开发、健身房会员管理平台、健身房设备管理系统、健身房系统功能优化

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

如实例布局图,如何做到两栏四列,margin间距超出了两段不对齐如何处理

使用 CSS Grid 实现两栏四列布局&#xff1a; <!DOCTYPE html><html lang"en"><head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <sty…

【原创】java+ssm+mysql在线文件管理系统设计与实现

个人主页&#xff1a;程序猿小小杨 个人简介&#xff1a;从事开发多年&#xff0c;Java、Php、Python、前端开发均有涉猎 博客内容&#xff1a;Java项目实战、项目演示、技术分享 文末有作者名片&#xff0c;希望和大家一起共同进步&#xff0c;你只管努力&#xff0c;剩下的交…

vue中实现css布局

在vue中通过flex布局实现css的s型结构 通过数组截取循环布局&#xff0c;奇数行从左到右&#xff0c;在偶数行从右到左实现s型结构 主要内容分为三部分 中间内容部分 数据格式 items: [{nodeList: [1, 2, 3, 4, 5, 6]},{nodeList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}, {nod…

数据结构与算法:贪心与相关力扣题455.分发饼干、376.摆动序列、53.最大子数组和(贪心+动态规划dp)、122.买卖股票的最佳时机Ⅱ

贪心算法 贪心策略在实现时&#xff0c;经常使用到的技巧&#xff1a; 根据某标准建立一个比较器来排序 根据某标准建立一个比较器来组成堆 举例题目&#xff1a;会议室安排 一些项目要占用一个会议室宣讲&#xff0c;会议室不能同时容纳两个项目的宣讲。 给你每一个项目开始…

Go 1.19.4 命令调用、日志、包管理、反射-Day 17

1. 系统命令调用 所谓的命令调用&#xff0c;就是通过os&#xff0c;找到系统中编译好的可执行文件&#xff0c;然后加载到内存中&#xff0c;变成进程。 1.1 exec.LookPath&#xff08;寻找命令&#xff09; 作用&#xff1a; exec.LookPath 函数用于在系统的环境变量中搜索可…

2024年中国工业大模型行业发展研究报告|附43页PDF文件下载

工业大模型伴随着大模型技术的发展&#xff0c;逐渐渗透至工业&#xff0c;处于萌芽阶段。 就大模型的本质而言&#xff0c;是由一系列参数化的数学函数组成的计算系统&#xff0c;且是一个概率模型&#xff0c;其工作机制是基于概率和统计推动进行的&#xff0c;而非真正的理解…

C++ 进阶:类相关特性的深入探讨

⭐在对C 中类的6个默认成员函数有了初步了解之后&#xff0c;现在我们进行对类相关特性的深入探讨&#xff01; &#x1f525;&#x1f525;&#x1f525;【C】类的默认成员函数&#xff1a;深入剖析与应用&#xff08;上&#xff09; 【C】类的默认成员函数&#xff1a;深入剖…

SpringBoot使用RestTemplate实现发送HTTP请求

Java 实现发送 HTTP 请求&#xff0c;系列文章&#xff1a; 《Java使用原生HttpURLConnection实现发送HTTP请求》 《Java使用HttpClient5实现发送HTTP请求》 《SpringBoot使用RestTemplate实现发送HTTP请求》 1、RestTemplate 的介绍 RestTemplate 是 Spring 框架提供的一个用…

【java】抽象类和接口(了解,进阶,到全部掌握)

各位看官早安午安晚安呀 如果您觉得这篇文章对您有帮助的话 欢迎您一键三连&#xff0c;小编尽全力做到更好 欢迎您分享给更多人哦 大家好我们今天来学习Java面向对象的的抽象类和接口&#xff0c;我们大家庭已经来啦~ 一&#xff1a;抽象类 1.1:抽象类概念 在面向对象的概念中…

12寸FAB厂试产到量产实现无纸化要素之软硬件

在12寸先进封装半导体车间从试产到量产的过程中&#xff0c;实现生产过程无纸化&#xff0c;需要综合考虑软硬件的配置。以下是一些关键的规划建议&#xff1a; 1、生产文档管理系统&#xff08;PDM&#xff09;&#xff1a; 采用基于SOLIDWORKS PDM开发的无纸化方案&#xf…

uniapp 整合 OpenLayers - 加载Geojson数据(在线、离线)

Geojson数据是矢量数据&#xff0c;主要是点、线、面数据集合 Geojson数据获取&#xff1a;DataV.GeoAtlas地理小工具系列 实现代码如下&#xff1a; <template><!-- 监听变量 operation 的变化&#xff0c;operation 发生改变时&#xff0c;调用 openlayers 模块的…