Leetcode 柱状图中最大的矩形

news2024/10/23 22:54:29

在这里插入图片描述

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

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

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

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

相关文章

使用Docker启动的Redis容器使用的配置文件路径等问题以及Python使用clickhouse_driver操作clickhouse数据库

一、使用Docker启动的Redis容器使用的配置文件路径等问题 1.docker启动的redis使用的配置文件路径是什么 使用docker搭建redis服务&#xff0c;本身redis启动的时候可以指定配置文件的&#xff0c; redis-server /指定配置文件路径/redis.conf。 但手上也没有一个redis配置文件…

SpringCloud无介绍快使用,sentinel服务熔断功能与持久化(二十四)

TOC 问题背景 从零开始学springcloud微服务项目 注意事项&#xff1a; 约定 > 配置 > 编码IDEA版本2021.1这个项目&#xff0c;我分了很多篇章&#xff0c;每篇文章一个操作步骤&#xff0c;目的是显得更简单明了controller调service&#xff0c;service调dao默认安装ngi…

JavaEE进阶----18.<Mybatis补充($和#的区别+数据库连接池)>

详解了 1.$和#的区别 2.数据库连接池。 3.简单了解MySQL企业开发规范 一、Mybatis面试题&#xff1a;$和#的区别是什么&#xff1f; MyBatis 参数赋值有两种方式&#xff0c;咱们前面使用了 #{} 进行赋值&#xff0c;接下来我们看下二者的区别。 1.1 #是预编译SQL&#xff0c;$…

序列化问题记录:Jackson 与 Fastjson 的注解

前言 Java 后端开发中&#xff0c;我们经常需要处理 JSON 序列化和反序列化的问题。Spring 框架默认使用 Jackson 作为 JSON 处理库&#xff0c;但在某些情况下&#xff0c;我们也可能会使用 Fastjson 来处理特定的序列化需求。由于这两种库的注解不完全兼容&#xff0c;因此在…

springboot扩展点都有哪些?

在Spring Boot中&#xff0c;扩展点指的是能够自定义或增强Spring Boot功能的机制。这些扩展点允许开发者在Spring Boot的基础设施之上做定制化配置、行为修改或增强。Spring Boot主要有以下几类扩展点&#xff1a; 1. ApplicationRunner 和 CommandLineRunner 这两个接口允许…

网络运输层之(1)TCP连接管理

网络运输层之(1)TCP连接管理 Author: Once Day Date: 2024年10月22日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏: 通信网络技术_Once-Day的博客…

【四】企业级JavaScript开发开发者控制台

多行输入 通常&#xff0c;当我们向控制台输入一行代码后&#xff0c;按 Enter&#xff0c;这行代码就会立即执行。 如果想要插入多行代码&#xff0c;请按 ShiftEnter 来进行换行。这样就可以输入长片段的 JavaScript 代码了。 总结 开发者工具允许我们查看错误、执行命令、…

【python爬虫课程设计】天气预报——分类数据爬取+数据可视化

一、选题的背景 随着人们对天气的关注逐渐增加&#xff0c;天气预报数据的获取与可视化成为了当今的热门话题&#xff0c;天气预报我们每天都会关注&#xff0c;天气情况会影响到我们日常的增减衣物、出行安排等。每天的气温、相对湿度、降水量以及风向风速是关注的焦点。通过…

【Linux】线程互斥与同步,生产消费模型(超详解)

目录 线程互斥 进程线程间的互斥相关背景概念 数据不一致问题 锁 深度理解锁 原理角度理解&#xff1a; 实现角度理解&#xff1a; 线程同步 条件变量 测试代码 生产消费模型 生产消费模型概念 编写生产消费模型 BlockingQueue &#xff08;1&#xff09;创建生产…

Dell服务器导入idrac 授权文件 (适用iDRAC7、iDRAC8、iDRAC9)

iDRAC Enterprise、iDRAC Datacenter 和 CMC Enterprise 的 30 天试用许可证,供熟悉高级功能集,例如使用虚拟控制台等 OpenManage Enterprise Advanced 或 Advanced+ 许可证支持高级功能,例如自动部署、服务器配置合规性和激活可用插件,如 OpenManage Enterprise Power Ma…

MySQL 数据库迁移至达梦 DM8 常见问题

目录 如何让迁移到 DM 的表名大小写和 MySQL 保持一致 MySQL 迁移到 DM 报错&#xff1a;列[NAMES]长度超出定义 MySQL 迁移到 DM 报错&#xff1a;记录超长 索引错误 DM大小写敏感配置 表空间 新建用户 用户与模式的关系 省略模式名的优势 实际操作 如何让迁移到 DM…

知识图谱的概念、特点及应用领域(详解)

目录 什么是知识图谱&#xff1f; 二、特点 三、应用领域 什么是知识图谱&#xff1f; 知识图谱&#xff08;Knowledge Graph&#xff09;是一种将知识进行结构化、组织和表示的方法&#xff0c;它利用图形模型表示事物之间的关系和属性。知识图谱通过节点&#xff08;实体&…

qt QWidget详解

一、概述 QWidget是容器组件&#xff0c;继承自QObject类和QPaintDevice类。能够绘制自己和处理用户输入&#xff0c;是QT中所有窗口组件类的父类&#xff0c;是所有窗口组件的抽象&#xff0c;每个窗口组件都是一个QWidget&#xff0c;QWidget类对象常用作父组件或顶级组件使…

T113 内核中 adbd相关配置1

准备工作 1. 配置 系统&#xff1a;ubuntu24.04docker&#xff08;ubuntu18.04&#xff09; 软件vscode, sdk:Tina-linux&#xff08;BingPi-M2&#xff09; 2. 构建环境直接使用自带的 source ./build/envsetup.sh lunch 选择 6 编译开启16线程 make -j16boot编译 mboot 打包…

关于jmeter中没有jp@gc - response times over time

1、问题如下&#xff1a; jmeter没有我们要使用的插件 2、解决方法&#xff1a; 选择下面文件&#xff0c;点击应用&#xff1b; 3、问题解决 ps&#xff1a;谢谢观看&#xff01;&#xff01;&#xff01;

力扣 简单 746.使用最小花费爬楼梯

文章目录 题目介绍题解 题目介绍 题解 思路分析&#xff1a; 确定dp数组以及下标的含义&#xff1a;dp[i]的定义为到达第i台阶所花费的最少体力。确定递推公式&#xff1a;可以有两个途径得到dp[i]&#xff0c;一个是dp[i-1] 一个是dp[i-2]。dp[i - 1] 跳到 dp[i] 需要花费 d…

玩转springboot之springboot异步执行

springboot异步执行 使用EnableAsync开启异步执行 在接口方法上使用Async注解进行标注&#xff0c;该接口是一个异步接口 自定义异步线程执行器 Configuration public class CustomAsyncConfigurer implements AsyncConfigurer {Overridepublic Executor getAsyncExecutor() {T…

WebGL编程指南 - 颜色与纹理

将顶点的其他&#xff08;非坐标&#xff09;数据——如颜色等——传入顶点着色器。 发生在顶点着色器和片元着色器之间的从图形到片元的转化&#xff0c;又称为图元光栅化 &#xff08;rasterzation process&#xff09;。 将图像&#xff08;或称纹理&#xff09;映射到图形…

C++笔记---哈希表

1. 哈希的概念 哈希(hash)又称散列&#xff0c;是一种组织数据的方式。从译名来看&#xff0c;有散乱排列的意思。 本质就是通过哈希函数把关键字Key跟存储位置建立一个映射关系&#xff0c;查找时通过这个哈希函数计算出Key存储的位置&#xff0c;进行快速查找。 STL中的un…

推荐IDE中实用AI编程插件,目前无限次使用

插件介绍 一款字节跳动推出的“基于豆包大模型的智能开发工具” 以vscode介绍【pycharm等都可以啊】&#xff0c;这个插件提供智能补全、智能预测、智能问答等能力&#xff0c;节省开发时间 直接在IDE中使用&#xff0c;就不用在网页中来回切换了 感觉还可以&#xff0c;响应速…