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

Studying-代码随想录训练营day49| 42. 接雨水、84.柱状图中最大的矩形

第49天,单调栈part02,两个很经典的例题,编程语言:C++

目录

42. 接雨水

84.柱状图中最大的矩形

总结: 


42. 接雨水

文档讲解:代码随想录接雨水

视频讲解:手撕接雨水

题目: 42. 接雨水 - 力扣(LeetCode)

学习:本题是非常巧妙的一道题,首先第一个重点是要理解题意,凹槽是存在左边比自己高,右边也比自己高的位置。本题有两个计算思路:按行计算和按列计算。

按照行计算:

按照列计算:

1.我们首先考虑按列计算的方法:按照列来计算的话,宽度一定是1了,我们再把每一列的雨水的高度求出来就可以了。(要注意第一个柱子和最后一个柱子是不接雨水的)

找到每个柱子左边和右边的最高柱子有两个办法:

①暴力求解:通过一个for循环,遍历当前柱子,两个for循环找到左边和右边最高的柱子,最后选取最小的柱子即可。

//时间复杂度O(n^2)
//空间复杂度O(1)
class Solution {
public:int trap(vector<int>& height) {int sum = 0;for (int i = 0; i < height.size(); i++) {// 第一个柱子和最后一个柱子不接雨水if (i == 0 || i == height.size() - 1) continue;int rHeight = height[i]; // 记录右边柱子的最高高度int lHeight = height[i]; // 记录左边柱子的最高高度for (int r = i + 1; r < height.size(); r++) {if (height[r] > rHeight) rHeight = height[r];}for (int l = i - 1; l >= 0; l--) {if (height[l] > lHeight) lHeight = height[l];}int h = min(lHeight, rHeight) - height[i];if (h > 0) sum += h;}return sum;}
};

②我们也可以采用双指针的方法,提前预处理好每个点左右最高的柱子,然后进行求解,能够降低时间复杂度。

//时间复杂度O(n)3n
//空间复杂度O(n)
class Solution {
public:int trap(vector<int>& height) {if (height.size() <= 2) return 0;vector<int> maxLeft(height.size(), 0);vector<int> maxRight(height.size(), 0);int size = maxRight.size();// 记录每个柱子左边柱子最大高度maxLeft[0] = height[0];for (int i = 1; i < size; i++) {maxLeft[i] = max(height[i], maxLeft[i - 1]);}// 记录每个柱子右边柱子最大高度maxRight[size - 1] = height[size - 1];for (int i = size - 2; i >= 0; i--) {maxRight[i] = max(height[i], maxRight[i + 1]);}// 求和int sum = 0;for (int i = 0; i < size; i++) {int count = min(maxLeft[i], maxRight[i]) - height[i];if (count > 0) sum += count;}return sum;}
};

2.除此之外,我们还可以考虑行计算的方式,采用的是单调栈的方法。

基于行计算,我们可以发现,如果从栈顶到栈底是从小到大的排列顺序的话。那么一旦发现添加的柱子高度大于栈头元素了,此时就出现凹槽了,栈头元素就是凹槽底部的柱子,栈头第二个元素就是凹槽左边的柱子,而添加的元素就是凹槽右边的柱子。下图给一个例子:

假如遇到相同的柱子,我们肯定需要把当前柱子加入栈中,前一个值相同的柱子可以选择去掉也可可以原则保留,对结果没有影响。因为我们计算的时候,左边柱子如果和本身一样的话,实际上最后求得的高度是0。

单调栈处理逻辑:

  • 情况一:当前遍历的元素(柱子)高度小于栈顶元素的高度 height[i] < height[st.top()]
  • 情况二:当前遍历的元素(柱子)高度等于栈顶元素的高度 height[i] == height[st.top()]
  • 情况三:当前遍历的元素(柱子)高度大于栈顶元素的高度 height[i] > height[st.top()]

我们需要针对这三种情况进行处理:

代码:

//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
public:int largestRectangleArea(vector<int>& heights) {if(heights.size() == 1) return heights[0];int result = 0; //记录结果stack<int> st; //单调栈,加入的是已经遍历的元素//我们需要首尾加0或者-1来完成首尾元素的遍历heights.insert(heights.begin(), 0); //头部加0heights.push_back(0); //尾部加0//我们需要找多左右第一个小于当前的值st.push(0);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(); //也可以不加这个,只是要多计算一次0st.push(i);}else {while(!st.empty() && heights[i] < heights[st.top()]) { //此处需要循环,不断找到合适的数值int mid = st.top();st.pop();if(!st.empty()) {int h = heights[mid];int w = i - st.top() - 1;result = max(result, h * w);}} st.push(i); //注意每一次都需要把当前下标加入}}return result;}
};

84.柱状图中最大的矩形

文档讲解:代码随想录柱状图中最大的矩形

视频讲解:手撕柱状图中最大的矩形

题目:代码随想录柱状图中最大的矩形

学习:本题实际上和上一题是一样的,上一题是找左右最大值,而本题则是找左右的最小值。每一个柱都可以向左右扩展到比它小的柱的位置。因此本题和上一题的唯一区别就在于,本题找的是左右的最小值,而上一题找的是左右的最大值。

因此本题的暴力解法和双指针优化方法和上一题的逻辑基本相同,再次就不过多赘述,我们主要讲解单调栈的方法。

由于本题是求解左右的最小值,因此本题单调栈里元素的顺序,从栈顶到栈底应该是从大到小的顺序!可以发现栈顶和栈顶的下一个元素以及要入栈的三个元素组成了我们要求最大面积的高度和宽度。

因此单调栈的处理逻辑的顺序也需要发生改变:

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

本题还需要注意一点,是需要在头部和尾部加一个0,用来处理第一个柱形和最后一个柱形。 例如如果没有在末尾加0的话,针对[2,4,6,8]的情况,我们就没办法找到最大的矩形8。

头部加0也是如此,为了处理第一个元素,防止其没有左边界。这是与上一题不同的,因为上一题不需要考虑下标0的左端和最后一个元素的右端。最后可以得到代码:

//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
public:int largestRectangleArea(vector<int>& heights) {if(heights.size() == 1) return heights[0];int result = 0; //记录结果stack<int> st; //单调栈,加入的是已经遍历的元素//我们需要首尾加0或者-1来完成首尾元素的遍历heights.insert(heights.begin(), 0); //头部加0heights.push_back(0); //尾部加0//我们需要找多左右第一个小于当前的值st.push(0);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(); //也可以不加这个,只是要多计算一次0st.push(i);}else {while(!st.empty() && heights[i] < heights[st.top()]) { //此处需要循环,不断找到合适的数值int mid = st.top();st.pop();if(!st.empty()) {int h = heights[mid];int w = i - st.top() - 1;result = max(result, h * w);}} st.push(i); //注意每一次都需要把当前下标加入}}return result;}
};

总结: 

牢记单调栈的作用:寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置。

牢记单调栈的处理逻辑有三部分:

  • 情况一:当前遍历的元素大于栈顶元素的情况
  • 情况二:当前遍历的元素等于栈顶元素的情况
  • 情况三:当前遍历的元素小于栈顶元素的情况

牢记以上两点,才能正确的写出单调栈。

其次更重要的是要理解题意,理解题目需要我们求解的到底是什么,多加练习,多加思考。 

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 手摸手教你撕碎西门子S7通讯协议15--开发自己的通讯库写数据
  • Android Studio的新界面,怎么切换回老界面
  • 记录一次使用Docker部署skywalking的过程
  • 基于Hadoop的服装电商数据分析系统【Hdfs、flume、HIve、sqoop、MySQL、echarts】
  • WebKit的媒体播放质量:打造高清流畅的Web体验
  • 防抖和节流
  • IC开发——RTL综合
  • oracle表、表空间使用空间
  • 什么是sql注入攻击,如何预防介绍一下mysql中的常见数据类型
  • 全面加速 GitHub,git clone 太慢的 9 种解决办法
  • Java高级Day19-List、ArrayList
  • A股探底强势反攻,量价齐声太漂亮
  • 基于SpringBoot+Vue的来访管理系统(带1w+文档)
  • 支持向量机(SVM)预测模型及其Python和MATLAB实现
  • Postman:API开发与测试的强大伴侣
  • Idea+maven+scala构建包并在spark on yarn 运行
  • Java 最常见的 200+ 面试题:面试必备
  • JDK 6和JDK 7中的substring()方法
  • Terraform入门 - 3. 变更基础设施
  • vue 配置sass、scss全局变量
  • Webpack 4 学习01(基础配置)
  • 大整数乘法-表格法
  • - 概述 - 《设计模式(极简c++版)》
  • 官方新出的 Kotlin 扩展库 KTX,到底帮你干了什么?
  • 解决jsp引用其他项目时出现的 cannot be resolved to a type错误
  • 码农张的Bug人生 - 见面之礼
  • 目录与文件属性:编写ls
  • 如何利用MongoDB打造TOP榜小程序
  • 手写一个CommonJS打包工具(一)
  • ​​​​​​​sokit v1.3抓手机应用socket数据包: Socket是传输控制层协议,WebSocket是应用层协议。
  • ‌‌雅诗兰黛、‌‌兰蔻等美妆大品牌的营销策略是什么?
  • ###C语言程序设计-----C语言学习(3)#
  • #Linux(帮助手册)
  • #常见电池型号介绍 常见电池尺寸是多少【详解】
  • (04)odoo视图操作
  • (12)Hive调优——count distinct去重优化
  • (附源码)springboot 个人网页的网站 毕业设计031623
  • (附源码)springboot教学评价 毕业设计 641310
  • (附源码)ssm户外用品商城 毕业设计 112346
  • (收藏)Git和Repo扫盲——如何取得Android源代码
  • (一)UDP基本编程步骤
  • (转)Windows2003安全设置/维护
  • *setTimeout实现text输入在用户停顿时才调用事件!*
  • *上位机的定义
  • .MSSQLSERVER 导入导出 命令集--堪称经典,值得借鉴!
  • .NET delegate 委托 、 Event 事件,接口回调
  • .net mvc actionresult 返回字符串_.NET架构师知识普及
  • .NET Windows:删除文件夹后立即判断,有可能依然存在
  • .net 打包工具_pyinstaller打包的exe太大?你需要站在巨人的肩膀上-VC++才是王道
  • .net 开发怎么实现前后端分离_前后端分离:分离式开发和一体式发布
  • .NET 设计模式初探
  • .Net 转战 Android 4.4 日常笔记(4)--按钮事件和国际化
  • .NET3.5下用Lambda简化跨线程访问窗体控件,避免繁复的delegate,Invoke(转)
  • .NET的数据绑定
  • [ Linux Audio 篇 ] 音频开发入门基础知识