代码随想录第六十三天 | 单调栈:寻找 左边 / 右边 距离当前元素最近的 更小 元素的 下标(暴力,双指针,单调栈)(84);代码随想录主要题目结束

1、寻找 左边 / 右边 距离当前元素最近的 更小 元素的 下标

1.1 leetcode 84:柱状图中最大的矩形

第一遍代码思路错了,如:输入[2,1,2],对于2,因为比栈顶元素1大,然后就会直接得出2(1,2),但是其实之前那个1也是可以用的,如果使用2,1,2就会得出3

错误思路:
单调栈内从栈顶到栈底 递增,因为除非加入的元素本身比所有可能的最大矩阵还大,不然后续计算就不需要管比栈顶元素大的元素

当加入的元素比栈顶大的时候:要比较自己的大小,之前最大矩阵的大小 和 与栈顶元素组成新的矩阵与最大矩阵加上自己后的大小,再考虑入不入栈
如果碰到等于栈顶元素的,直接不用入栈,因为对矩阵的高度不会产生影响,直接加一个栈顶元素即可
如果碰到小于栈顶元素的,比较自己加入后有没有使最大矩阵变大(高度变低长度加一),有就入栈更新最大矩阵,没有直接不入

碰到小于栈顶元素的情况:比较自己加入后有没有使最大矩阵变大(高度变低长度加一),有就更新最大矩阵,有或者没有都直接入栈
但是发现对于栈内从栈顶到栈底递增的单调栈来说,没有办法计算加入之后的矩阵,因为不知道上一个比自己小的元素是谁
再构建一个从栈顶到栈底单调递减的栈,这个栈单纯找到左边第一个更小的元素的下标即可

if(res == mat1) 加入的元素本身比所有可能的最大矩阵还大,直接清空栈加入这个元素

class Solution {
public:int largestRectangleArea(vector<int>& heights) {/*单调栈内从栈顶到栈底 递增,因为除非加入的元素本身比所有可能的最大矩阵还大,不然后续计算就不需要管比栈顶元素大的元素当加入的元素比栈顶大的时候:要比较自己的大小,之前最大矩阵的大小 和 与栈顶元素组成新的矩阵与最大矩阵加上自己后的大小,再考虑入不入栈如果碰到等于栈顶元素的,直接不用入栈,因为对矩阵的高度不会产生影响,直接加一个栈顶元素即可如果碰到小于栈顶元素的,比较自己加入后有没有使最大矩阵变大(高度变低长度加一),有就入栈更新最大矩阵,没有直接不入*/if(heights.size() == 0) return 0;int res = heights[0];stack<int> st;st.push(0);stack<int> st2;//栈顶到栈底递减 单调栈st2.push(heights.size() - 1);vector<int> getMin(heights.size(), -1);//找左边更小元素的下标for(int i = heights.size() - 2; i >= 0; i--) {while(!st2.empty() && heights[i] < heights[st2.top()]) {getMin[st2.top()] = i;st2.pop();}st2.push(i);}for(int i = 1; i < heights.size(); i++) {if(heights[i] == heights[st.top()]) {res += heights[i];}else if(heights[i] > heights[st.top()]) {int mat1 = heights[i];int mat2 = res;int mat3 = heights[st.top()] * (i - st.top() + 1);res = max(mat1, max(mat2, mat3));if(res == mat1) {//加入的元素本身比所有可能的最大矩阵还大,直接清空栈加入这个元素while(!st.empty()) {st.pop();}st.push(i);}}else {st.push(i);/*比较自己加入后有没有使最大矩阵变大(高度变低长度加一),有就更新最大矩阵,有或者没有都直接入栈但是发现对于栈内从栈顶到栈底递增的单调栈来说,没有办法计算加入之后的矩阵,因为不知道上一个比自己小的元素是谁再构建一个从栈顶到栈底单调递减的栈,这个栈单纯找到左边第一个更小的元素的下标即可*/int mat = 0;if(getMin[st.top()] == -1) {mat = heights[i] * (i + 1);}else {mat = heights[i] * (i - getMin[st.top()]);}if(res < mat) {res = mat;}}}return res;}
};

1.2 leetcode 84:暴力解法

其实跟昨天 leetcode 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;}
};

1.3 leetcode 84:双指针解法

根据思路写代码:
记录左边 第一个 更小的节点 下标vector<int> leftMin(heights.size(), -1);
记录右边 第一个 更小的节点 下标vector<int> rightMin(heights.size(), -1);
注意数组记录的是 下标 不是元素值

注意跟 leetcode 42:接雨水 不同不是找左边最大值了,找最近的更小的值,所以不是用if,而是不断向左遍历更小元素。寻找的过程中,一旦小了就停,而且记录的是 下标 不是 数值大小

for(int i = 1; i < heights.size(); i++) {int t = i - 1;while(t >= 0 && heights[t] >= heights[i]) {t = leftMin[t];}leftMin[i] = t;
}

完整代码:

class Solution {
public:int largestRectangleArea(vector<int>& heights) {vector<int> leftMin(heights.size(), -1);//记录左边第一个更小的节点 下标vector<int> rightMin(heights.size(), -1);//记录右边第一个更小的节点 下标//注意数组记录的是下标不是元素值for(int i = 1; i < heights.size(); i++) {//注意跟 leetcode 42:接雨水 不同,不是找左边最小值了,找最近的更小的值,所以不是用if,而是不断向左遍历更小元素寻找的过程,一旦小了就停int t = i - 1;while(t >= 0 && heights[t] >= heights[i]) {t = leftMin[t];}leftMin[i] = t;}for(int i = heights.size() - 2; i >= 0; i--) {int t = i + 1;while(t < heights.size() && heights[t] >= heights[i]) {t = rightMin[t];}if(t < heights.size()) {rightMin[i] = t;}else {rightMin[i] = -1;}}int res = 0;for(int i = 0; i < heights.size(); i++) {int left = 0;if(leftMin[i] == -1) {left = -1;}else {left = leftMin[i];}int right = 0;if(rightMin[i] == -1) {right = heights.size();}else {right = rightMin[i];}int s = heights[i] * (right - left - 1);res = max(res, s);}return res;}
};

代码随想录思路及代码:
本题双指针的写法整体思路leetcode 42:接雨水 是一致的,但要比 leetcode 42:接雨水 难一些
难就难在本题要记录记录每个柱子 左边第一个 小于 该柱子的下标,而不是左边第一个小于该柱子的高度
所以需要循环查找,也就是下面在寻找的过程中使用了while

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;}
};

1.4 leetcode 84:单调栈

本题单调栈的解法和接雨水的题目遥相呼应
为什么这么说呢,leetcode 42:接雨水 是找每个柱子左右两边第一个大于该柱子高度的柱子,而本题是找每个柱子左右两边第一个小于该柱子的柱子

根据思路写代码报错:

class Solution {
public:int largestRectangleArea(vector<int>& heights) {//递减的单调栈,等发现当前元素大于栈顶元素,说明找到右边第一个更大的了int res = heights[0];stack<int> st; st.push(0);for(int i = 1; i < heights.size(); i++) {if(heights[i] >= heights[st.top()]) {st.push(i);}else {while (!st.empty() && heights[i] < heights[st.top()]) { 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];res = max(res, w * h);}}st.push(i);}}//对于剩下的在栈内递减的元素的处理if(!st.empty()) {int rightIndex = st.top();while(st.size() > 1) {st.pop();}int leftIndex = st.top();int mat_s2 = heights[st.top()] * (rightIndex - leftIndex + 1);if(res < mat_s2) res = mat_s2;}return res;}
};

对剩下的在栈内的元素处理出了问题,因为不一定是最小的那个元素乘以长度就是最大,还可能是某几个连续元素画矩阵最大
还是在头尾加两个0元素靠谱,这样保证所有非0元素都可以被计算过

对于这段代码一开始没理解,说明之前暴力双指针的部分也没理解透:为什么矩阵的高是三个元素中的最大值,以[2,1,5,6,2,3]为例,其实对于5、6,遍历到2的时候,其实算了两次才到10;先算的6,结果6;然后算的5,5*2才是最终结果10,矩阵的高其实是栈中最高的那个元素的大小

else {while (!st.empty() && heights[i] < heights[st.top()]) { 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];res = max(res, w * h);}}st.push(i);
}

修改后的完整代码如下:

class Solution {
public:int largestRectangleArea(vector<int>& heights) {//递减的单调栈,等发现当前元素大于栈顶元素,说明找到右边第一个更大的了heights.insert(heights.begin(), 0); // 数组头部加入元素0heights.push_back(0); // 数组尾部加入元素0int res = heights[0];stack<int> st; st.push(0);for(int i = 1; i < heights.size(); i++) {if(heights[i] >= heights[st.top()]) {st.push(i);}else {while (!st.empty() && heights[i] < heights[st.top()]) { 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];res = max(res, w * h);}}st.push(i);}}return res;}
};

代码随想录详细思路:
本地单调栈的解法和接雨水的题目是遥相呼应的
为什么这么说呢,leetcode 42:接雨水 是找每个柱子左右两边第一个大于该柱子高度的柱子,而本题是找每个柱子左右两边第一个小于该柱子的柱子

这里就涉及到了单调栈很重要的性质,就是单调栈里的顺序,是从小到大还是从大到小
在 leetcode 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); // 数组头部加入元素0heights.push_back(0); // 数组尾部加入元素0st.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()]) { // 注意是whileint 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;}
};

首先来说末尾为什么要加元素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 进行比较,周而复始,那么计算的最后结果result就是0。 如图所示:
数组本身是降序的,为了避免空栈取值,直接跳过了计算结果的逻辑
所以我们需要在 height数组前后各加一个元素0

2、代码随想录主要题目结束

曲曲折折写到这里,人间忽晚,山河已秋
其实也就三个月不到

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

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

相关文章

etoken是什么意思,有什么作用?

EToken是一种数字货币&#xff0c;它是由以太坊区块链平台发行的智能合约&#xff0c;旨在为以太坊生态系统提供一种安全、可靠、去中心化的交易媒介。EToken具有多种作用&#xff0c;下面将详细介绍。 一、EToken的定义和发行 EToken是由以太坊智能合约创建的数字货币&#xf…

渲染器——快速Diff算法

讨论第三种用于比较新旧两组子节点的方式&#xff1a;快速Diff 算法。正如其名&#xff0c;该算法的实测速度非常快。该算法最早应用于 ivi 和 inferno 这两个框架&#xff0c;Vue.js 3 借鉴并扩展了它。 下图比较了 ivi、inferno 以及 Vue.js 2 的性能&#xff1a; 上图来自…

Redis持久化机制详解

使用缓存的时候&#xff0c;我们经常需要对内存中的数据进行持久化也就是将内存中的数据写入到硬盘中。大部分原因是为了之后重用数据&#xff08;比如重启机器、机器故障之后恢复数据&#xff09;&#xff0c;或者是为了做数据同步&#xff08;比如 Redis 集群的主从节点通过 …

Qt程序的自定义安装卸载方案

前言 NSIS 是一个 Open Source 的 Windows 系统下安装程序制作程序&#xff1b; NSIS-UI-Plugin 是一个开源的NSIS UI插件&#xff1b; 0x0 环境搭建 https://www.cnblogs.com/NSIS/p/16581122.html https://github.com/sway913/NSIS-UI-Plugin 0x1 类图 0x2 二次开发 自定…

持续集成失败:hudson.plugins.git.GitException: Failed to delete workspace

持续集成环境(git gitlab jenkins pipeline maven harbor docker k8s)之前都是ok的&#xff0c;突然就报错了&#xff1a; Cloning the remote Git repository Cloning repository git192.168.117.180:qzcsbj/gift.git ERROR: Failed to clean the workspace jenkins.ut…

【开源】基于Vue和SpringBoot的高校宿舍调配管理系统

项目编号&#xff1a; S 051 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S051&#xff0c;文末获取源码。} 项目编号&#xff1a;S051&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能需求2.1 学生端2.2 宿管2.3 老师端 三、系统…

【C++】C++11(2)

文章目录 一、新的类功能二、可变参数模板&#xff08;了解&#xff09;三、lambda表达式1. C98中的一个例子2.lambda表达式3.lambda表达式语法4.函数对象与lambda表达式 四、包装器1.function包装器2.bind 五、线程库1.thread类的简单介绍2.线程函数参数3.原子性操作库(atomic…

【SEO学习】技术总结

我们已经涵盖了几乎所有与搜索引擎优化相关的主要概念。现在您也熟悉了最常用的 SEO 相关术语。 您已经学会了如何从 SEO 的角度优化关键字、标题、alt、元标签、锚和其他文本。您还了解了在您的网站中拥有优质内容的重要性。在“杂项技术”一章中&#xff0c;我们为您提供了其…

泛型概述(下):泛型实现机制

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 上篇提到泛型可以看做是…

kafka权限认证 topic权限认证 权限动态认证-亲测成功

kafka权限认证 topic权限认证 权限动态认证-亲测成功 kafka动态认证 自定义认证 安全认证-亲测成功 MacBook Linux安装Kafka Linux解压安装Kafka 介绍 1、Kafka的权限分类 身份认证&#xff08;Authentication&#xff09;&#xff1a;对client 与服务器的连接进行身份认证…

代码随想录算法训练营第五十天| 309.最佳买卖股票时机含冷冻期 714.买卖股票的最佳时机含手续费

文档讲解&#xff1a;代码随想录 视频讲解&#xff1a;代码随想录B站账号 状态&#xff1a;看了视频题解和文章解析后做出来了 309.最佳买卖股票时机含冷冻期 class Solution:def maxProfit(self, prices: List[int]) -> int:n len(prices)if n < 2:return 0dp [[0]*3…

vue2【axios请求】

1&#xff1a;axios作用 axios&#xff08;发音&#xff1a;艾克c奥斯&#xff09;是前端圈最火的&#xff0c;专注于数据请求的库。 Axios 是一个基于 promise 的 HTTP 库&#xff0c;可以用在浏览器和 node.js 中axios的github:https://github.com/axios/axios 中文官网地址…

【opencv】计算机视觉:停车场车位实时识别

目录 目标 整体流程 背景 详细讲解 目标 我们想要在一个实时的停车场监控视频中&#xff0c;看看要有多少个车以及有多少个空缺车位。然后我们可以标记空的&#xff0c;然后来车之后&#xff0c;实时告诉应该停在那里最方便、最近&#xff01;&#xff01;&#xff01;实现…

C++ 使用c++类模板实现动态数组-可实现自定义数据类型存储

.hpp文件 #include <iostream> #include <cstdlib> #include <cstring> using namespace std; template <class T> class arraylist { private:T* data ;//数组地址int size;//长度int count;//容量public:arraylist();~arraylist();void add(T t);T&…

GitHub 报告发布:TypeScript 取代 Java 成为第三受欢迎语言

GitHub发布的2023年度Octoverse开源状态报告发布&#xff0c;研究围绕AI、云和Git的开源活动如何改变开发人员体验&#xff0c;以及在开发者和企业中产生的影响。报告发现了三大趋势&#xff1a; 1、生成式AI的广泛应用&#xff1a; 开发人员大量使用生成式AI进行构建。越来越…

[Linux] 进程入门

&#x1f4bb;文章目录 &#x1f4c4;前言计算机的结构体系与概念冯诺依曼体系结构操作系统概念目的与定位 进程概念描述进程-PCBtask_struct检查进程利用fork创建子进程 进程状态进程状态查看僵尸进程孤儿进程 &#x1f4d3;总结 &#x1f4c4;前言 作为一名程序员&#xff0c…

Python 跨文件夹导入自定义包

一、问题再现 有时我们自己编写一些模块时&#xff0c;跨文件夹调用会出现ModuleNotFoundError: No module named XXX 二、解决方案 只需要在下层文件夹中的__init__.py文件中&#xff0c;添加如下代码即可&#xff1a; import sys from os import path sys.path.append(pa…

单链表OJ题——11.随机链表的复制

11.随机链表的复制 138. 随机链表的复制 - 力扣&#xff08;LeetCode&#xff09; /* 解题思路&#xff1a; 此题可以分三步进行&#xff1a; 1.拷贝链表的每一个节点&#xff0c;拷贝的节点先链接到被拷贝节点的后面 2.复制随机指针的链接&#xff1a;拷贝节点的随机指针指向…

板块概念相关(五)

5-板块概念相关 文章目录 5-板块概念相关一. 查询所有的版块列表二. 查询所有的概念列表三. 查询所有的地域列表四. 查询所有的版块资金支持的类型五. 查询某个版块历史记录列表,形成图表形式六. 查询某个版块历史记录列表七. 查询某个版块今日资金,形成图表形式八. 查询该板块…

【Python爬虫】8大模块md文档集合从0到scrapy高手,第7篇:selenium 数据提取详解

本文主要学习一下关于爬虫的相关前置知识和一些理论性的知识&#xff0c;通过本文我们能够知道什么是爬虫&#xff0c;都有那些分类&#xff0c;爬虫能干什么等&#xff0c;同时还会站在爬虫的角度复习一下http协议。 爬虫全套笔记地址&#xff1a; 请移步这里 共 8 章&#x…