算法训练第六十天 | 84.柱状图中最大的矩形

news2024/11/26 21:39:05

单调栈part03

  • 84.柱状图中最大的矩形
    • 题目描述
    • 思路
      • 暴力解法
      • 双指针解法
      • 单调栈

84.柱状图中最大的矩形

题目链接:84.柱状图中最大的矩形
参考:https://programmercarl.com/0084.%E6%9F%B1%E7%8A%B6%E5%9B%BE%E4%B8%AD%E6%9C%80%E5%A4%A7%E7%9A%84%E7%9F%A9%E5%BD%A2.html

题目描述

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。
在这里插入图片描述
在这里插入图片描述

  • 1 <= heights.length <=10^5
  • 0 <= heights[i] <= 10^4

思路

本题和42. 接雨水 ,是遥相呼应的两道题目,建议都要仔细做一做,原理上有很多相同的地方,但细节上又有差异,更可以加深对单调栈的理解!

我们先来看一下暴力解法的解法:

暴力解法

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        int sum = 0;
        for (int i = 0; i < heights.size(); i++) {
            int left = i;
            int right = i;
            for (; left >= 0; left--) {
                if (heights[left] < heights[i]) break;
            }
            for (; right < heights.size(); right++) {
                if (heights[right] < heights[i]) break;
            }
            int w = right - left - 1;
            int h = heights[i];
            sum = max(sum, w * h);
        }
        return sum;
    }
};

如上代码并不能通过leetcode,超时了,因为时间复杂度是 O ( n 2 ) O(n^2) O(n2)

双指针解法

本题双指针的写法整体思路和42. 接雨水 是一致的,但要比42. 接雨水 难一些。

难就难在本题要记录记录每个柱子 左边第一个小于该柱子的下标,而不是左边第一个小于该柱子的高度。

所以需要循环查找,也就是下面在寻找的过程中使用了while,详细请看下面注释,整理思路在题解:42. 接雨水 中已经介绍了。

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        vector<int> minLeftIndex(heights.size());
        vector<int> minRightIndex(heights.size());
        int size = heights.size();

        // 记录每个柱子 左边第一个小于该柱子的下标
        minLeftIndex[0] = -1; // 注意这里初始化,防止下面while死循环
        for (int i = 1; i < size; i++) {
            int t = i - 1;
            // 这里不是用if,而是不断向左寻找的过程
            while (t >= 0 && heights[t] >= heights[i]) t = minLeftIndex[t];
            minLeftIndex[i] = t;
        }
        // 记录每个柱子 右边第一个小于该柱子的下标
        minRightIndex[size - 1] = size; // 注意这里初始化,防止下面while死循环
        for (int i = size - 2; i >= 0; i--) {
            int t = i + 1;
            // 这里不是用if,而是不断向右寻找的过程
            while (t < size && heights[t] >= heights[i]) t = minRightIndex[t];
            minRightIndex[i] = t;
        }
        // 求和
        int result = 0;
        for (int i = 0; i < size; i++) {
            int sum = heights[i] * (minRightIndex[i] - minLeftIndex[i] - 1);
            result = max(sum, result);
        }
        return result;
    }
};

单调栈

本地单调栈的解法和接雨水的题目是遥相呼应的。

为什么这么说呢,42. 接雨水 是找每个柱子左右两边第一个大于该柱子高度的柱子,而本题是找每个柱子左右两边第一个小于该柱子的柱子。

这里就涉及到了单调栈很重要的性质,就是单调栈里的顺序,是从小到大还是从大到小。

在题解42. 接雨水 中接雨水的单调栈从栈头(元素从栈头弹出)到栈底的顺序应该是从小到大的顺序。

那么因为本题是要找每个柱子左右两边第一个小于该柱子的柱子,所以从栈头(元素从栈头弹出)到栈底的顺序应该是从大到小的顺序!

我来举一个例子,如图:
在这里插入图片描述
只有栈里从大到小的顺序,才能保证栈顶元素找到左右两边第一个小于栈顶元素的柱子。

所以本题单调栈的顺序正好与接雨水反过来。

此时大家应该可以发现其实就是栈顶和栈顶的下一个元素以及要入栈的三个元素组成了我们要求最大面积的高度和宽度

理解这一点,对单调栈就掌握的比较到位了。

除了栈内元素顺序和接雨水不同,剩下的逻辑就都差不多了。

主要就是分析清楚如下三种情况:

  • 情况一:当前遍历的元素heights[i]大于栈顶元素heights[st.top()]的情况
  • 情况二:当前遍历的元素heights[i]等于栈顶元素heights[st.top()]的情况
  • 情况三:当前遍历的元素heights[i]小于栈顶元素heights[st.top()]的情况

C++代码如下:

// 版本一
class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        int result = 0;
        stack<int> st;
        heights.insert(heights.begin(), 0); // 数组头部加入元素0
        heights.push_back(0); // 数组尾部加入元素0
        st.push(0);

        // 第一个元素已经入栈,从下标1开始
        for (int i = 1; i < heights.size(); i++) {
            if (heights[i] > heights[st.top()]) { // 情况一
                st.push(i);
            } else if (heights[i] == heights[st.top()]) { // 情况二
                st.pop(); // 这个可以加,可以不加,效果一样,思路不同
                st.push(i);
            } else { // 情况三
                while (!st.empty() && heights[i] < heights[st.top()]) { // 注意是while
                    int mid = st.top();
                    st.pop();
                    if (!st.empty()) {
                        int left = st.top();
                        int right = i;
                        int w = right - left - 1;
                        int h = heights[mid];
                        result = max(result, w * h);
                    }
                }
                st.push(i);
            }
        }
        return result;
    }
};

在 height数组上后,都加了一个元素0, 为什么这么做呢?

首先来说末尾为什么要加元素0?

如果数组本身就是升序的,例如[2,4,6,8],那么入栈之后 都是单调递减,一直都没有走 情况三 计算结果的哪一步,所以最后输出的就是0了。 如图:
在这里插入图片描述
那么结尾加一个0,就会让栈里的所有元素,走到情况三的逻辑。

开头为什么要加元素0?

如果数组本身是降序的,例如 [8,6,4,2],在 8 入栈后,6 开始与8 进行比较,此时我们得到 mid(8),rigt(6),但是得不到 left。

(mid、left,right 都是对应版本一里的逻辑)

因为 将 8 弹出之后,栈里没有元素了,那么为了避免空栈取值,直接跳过了计算结果的逻辑。

之后又将6 加入栈(此时8已经弹出了),然后 就是 4 与 栈口元素 8 进行比较,周而复始,那么计算的最后结果resutl就是0。 如图所示:
在这里插入图片描述
所以我们需要在 height数组前后各加一个元素0。

版本一代码精简之后:

// 版本二
class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        stack<int> st;
        heights.insert(heights.begin(), 0); // 数组头部加入元素0
        heights.push_back(0); // 数组尾部加入元素0
        st.push(0);
        int result = 0;
        for (int i = 1; i < heights.size(); i++) {
            while (heights[i] < heights[st.top()]) {
                int mid = st.top();
                st.pop();
                int w = i - st.top() - 1;
                int h = heights[mid];
                result = max(result, w * h);
            }
            st.push(i);
        }
        return result;
    }
};

依然建议大家按部就班把版本一写出来,把情况一二三分析清楚,然后在精简代码到版本二。 直接看版本二容易忽略细节!

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

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

相关文章

《Kubernetes部署篇:Ubuntu20.04基于containerd二进制部署K8S 1.24.12集群(一主多从)》

一、架构图 如下图所示&#xff1a; 如下图所示&#xff1a; 二、环境信息 1、部署规划 主机名IP地址操作系统内核版本软件说明etcd01192.168.1.62Ubuntu 20.04.5 LTS5.15.0-69-genericetcdetcd02192.168.1.63Ubuntu 20.04.5 LTS5.15.0-69-genericetcdetcd03192.168.1.64Ubunt…

kettle链接mysql Public Key Retrieval is not allowed

kettle 报错信息页面&#xff1a; 出现 Public Key Retrieval 的场景可以概括为在禁用 SSL/TLS 协议传输切当前用户在服务器端没有登录缓存的情况下&#xff0c;客户端没有办法拿到服务器的公钥。具体的场景如下&#xff1a; 新建数据库用户&#xff0c;首次登录&#xff1b;数…

课程推荐 | 机器视觉与边缘计算应用

点击蓝字关注我们,让开发变得更有趣文案 | 李擎排版 | 李擎文案来源 | https://www.icourse163.org/course/FUDAN-1456632162OpenVINO™╱ 前言 ╱机器视觉是目前人工智能重要的应用领域&#xff0c;在很多领域都有丰富的成功应用案例。其中深度学习的目标检测算法是非常实用的…

ubuntu(20.04)-shell脚本(1)-基本概念

目录 1.概述 2.shell脚本调用形式 3.shell语法初识 3.1 定义以开头&#xff1a;#&#xff01;/bin/bash 3.2 单个“#”号代表注释当前行 4.变量 4.1 只读变量 4.2 环境变量&#xff1a; env 4.3 预测变量&#xff1a; 4.4 变量扩展&#xff1a; 是否存在&#xff0c;字符串…

通过JMH框架 测试公平锁与非公平锁的性能(附测试代码和源码分析)

目录 先上测试代码&#xff1a; 上依赖&#xff1a; 输出结果&#xff1a;(注意不要debug运行&#xff0c;直接运行代码&#xff0c;否则报错) 源码-公平锁的 lock 方法&#xff1a; 源码-非公平锁的lock方法&#xff1a; 总结 非公平锁和公平锁的两处不同&#xff1a; …

docker入门之一:docker基础概念与安装

1. Docker简单介绍 1.1. 什么是docker&#xff1f;1.2. Docker和传统虚拟机1.3. 为什么使用docker1.4. docker架构 2. Docker安装 2.1. docker版本命名2.2. docker安装2.3. docker卸载2.4. docker镜像加速器 1. Docker简单介绍 1.1. 什么是docker&#xff1f; google go语言…

24-Tomcat

目录 1.Tomcat是什么&#xff1f; 2.版本号 3.下载 4.目录介绍 4.1.bin目录 4.2.conf目录 4.3.logs目录 4.4.webapps目录 5.启动服务器 PS&#xff1a;解决Tomcat乱码问题 PS&#xff1a;Tomcat点击启动&#xff0c;控制台一闪而过&#xff0c;啥也没有解决方案 PS…

【花雕学AI】4月5日,ChatGPT中国财经背景分析:昨天沪指重返3300点,这说明了什么?

在这里插入图片描述 附录&#xff1a; 一、ChatGPT是一个可以和你聊天的人工智能程序&#xff0c;它可以用文字回答你的问题&#xff0c;也可以根据你的提示写出文章、歌词、代码等内容。ChatGPT是由一个叫OpenAI的机构开发的&#xff0c;它使用了一种叫做GPT的技术&…

TCP协议的相关特性(续)

TCP协议的相关特性&#x1f50e;滑动窗口&#x1f50e;流量控制&#x1f50e;拥塞控制&#x1f50e;延时应答&#x1f50e;捎带应答&#x1f50e;面向字节流(粘包问题)&#x1f50e;异常情况&#x1f50e;总结关于 确认应答 超时重传, 连接管理 请参考: 点击这里 &#x1f50e…

IT知识百科:什么是基站?

一、基站介绍 基站&#xff08;Base Station&#xff09;&#xff0c;也称为基站站点或基站设备&#xff0c;是无线通信网络中的关键设备之一。基站用于与移动设备&#xff08;如手机、无线网卡等&#xff09;进行通信和数据传输&#xff0c;实现无线通信覆盖。 二、基站的功…

基于Python机器学习、深度学习技术提升气象、海洋、水文领域实践应用

Python是功能强大、免费、开源&#xff0c;实现面向对象的编程语言&#xff0c;能够在不同操作系统和平台使用&#xff0c;简洁的语法和解释性语言使其成为理想的脚本语言。除了标准库&#xff0c;还有丰富的第三方库&#xff0c;Python在数据处理、科学计算、数学建模、数据挖…

Web服务器压力测试

Web服务器压力测试 使用WebBench去进行网站的压力测试 1、去github下载项目源码webbench 2、download到本地 3、将压缩包上传到虚拟机上 4、解压&#xff0c;使用命令&#xff1a;unzip 压缩包名 5、 cd WebBench-mask6、构建项目 makemake install7、上述动作完成后&…

【Vue3】如何用Vue CLI 创建一个Vue3的初始化项目

第一步、安装Vue Cli npm install -g vue/cli 安装成功后&#xff0c;就可以在命令行工具中&#xff0c;使用vue命令。 检测是否安装成功&#xff0c;可以用 vue -V 出现版本号&#xff0c;代表安装成功。 第二步、创建项目 为了方便管理项目&#xff0c;我先在 github 创建了…

队列实现图书信息管理(C语言)

文章目录Queue.hmain.cQueue.c用队列实现一个图书信息管理&#xff0c;这里放一下有哪些文件。&#xff08;ps&#xff1a;我之前写的是学生信息管理&#xff0c;但是有人说我们的作业是写图书&#xff0c;就该了下内容&#xff0c;没有改文件名&#xff09;队列是用链表实现的…

***大论文中插入Visio不失真方法:word插入viso图片方法

***大论文中插入Visio不失真方法&#xff1a;word插入viso图片方法1、可以直接导出emf2、如果利用emf导致字符间距过大&#xff0c;可以选择下面方式1、可以直接导出emf 导出emf方法&#xff1a; 打开visio --> 另存为 --> 选择emf格式文件 打开word --> 插入图片&a…

6 计时器(三)

6.4 输出比较演示** 演示1&#xff1a;PWM驱动呼吸灯** 函数解释&#xff1a; 输出比较单元&#xff08;掌握&#xff09; void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct); void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCIni…

对Redis 的数据结构的更深刻理解

文章目录简单动态字符串SDS与C字符串的区别链表字典哈希算法 —— 添加新键值对的过程rehashrehash一般过程渐进式rehash渐进式rehash的详细步骤跳跃表实现整数集合intset升级步骤升级好处降级压缩列表 ziplistziplistnode连锁更新对象字符串对象列表对象哈希对象编码转换集合对…

RK356X 解除UVC摄像头预览分辨率1080P限制

平台 RK3566 Android 11 概述 UVC&#xff1a; USB video class&#xff08;又称为USB video device class or UVC&#xff09;就是USB device class视频产品在不需要安装任何的驱动程序下即插即用&#xff0c;包括摄像头、数字摄影机、模拟视频转换器、电视卡及静态视频相机…

详解C++中的命名空间(namespace)

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【C之路】 目录C关键字(C98)命名冲突命名空间命名空间的定义局部域和全局域的关系命名空间域小结命名空间中可以定义哪些内容嵌套命名空间…

音视频开发常用分析工具介绍

综述 工欲善其事&#xff0c;必先利其器&#xff1b;兵马未到&#xff0c;粮草先行。 在音视频开发过程中&#xff0c;利用工具可以更方便、更直观、更快捷的分析音视频的数据&#xff0c;便于开发过程中分析、调试和解决问题。 现总结一些音视频开发过程中常用的分析工具。…