从《lc42 接雨水》到《lc84 柱状图中的最大矩形》

1 LC42 接雨水

在这里插入图片描述

1.1 答案

解法四:双指针
动态规划中,我们常常可以对空间复杂度进行进一步的优化。

例如这道题中,可以看到,max_left [ i ] 和 max_right [ i ] 数组中的元素我们其实只用一次,然后就再也不会用到了。所以我们可以不用数组,只用一个元素就行了。我们先改造下 max_left。

public int trap(int[] height) {int sum = 0;int max_left = 0;int[] max_right = new int[height.length];for (int i = height.length - 2; i >= 0; i--) {max_right[i] = Math.max(max_right[i + 1], height[i + 1]);}for (int i = 1; i < height.length - 1; i++) {max_left = Math.max(max_left, height[i - 1]);int min = Math.min(max_left, max_right[i]);if (min > height[i]) {sum = sum + (min - height[i]);}}return sum;
}作者:windliang
链接:https://leetcode.cn/problems/trapping-rain-water/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

我们成功将 max_left 数组去掉了。但是会发现我们不能同时把 max_right 的数组去掉,因为最后的 for 循环是从左到右遍历的,而 max_right 的更新是从右向左的。

所以这里要用到两个指针,left 和 right,从两个方向去遍历。

那么什么时候从左到右,什么时候从右到左呢?根据下边的代码的更新规则,我们可以知道

max_left = Math.max(max_left, height[i - 1]);

height [ left - 1] 是可能成为 max_left 的变量, 同理,height [ right + 1 ] 是可能成为 right_max 的变量。

只要保证 height [ left - 1 ] < height [ right + 1 ] ,那么 max_left 就一定小于 max_right。

因为 max_left 是由 height [ left - 1] 更新过来的,而 height [ left - 1 ] 是小于 height [ right + 1] 的,而 height [ right + 1 ] 会更新 max_right,所以间接的得出 max_left 一定小于 max_right。

反之,我们就从右到左更。

  public int trap(int[] height) {int n=height.length;int ans=0;int lm=-1,rm=-1;int l=1,r=n-2;while(l<=r){if(height[l-1]<height[r+1]){ // #Alm=Math.max(lm,height[l-1]);if(lm>height[l]){ans+=lm-height[l];}l++;}else{rm=Math.max(rm,height[r+1]);if(rm>height[r]){ans+=rm-height[r];}r--;}}return ans;}

1.2 解析 #A 处的代码if(height[l-1]<height[r+1]), max_left不是还没有基于height[left - 1]更新吗,为什么能做出(由于max_left是基于高度[left - 1]更新的,并且高度[left - 1]小于高度[right + 1],这更新了max_right,因此逻辑上可以得出max_left将小于或等于max_right。)的推断?这里是不是默认初始以及之后的max_left <=max_right,所以只需要比较height[left - 1]和heightright- 1

您的理解是正确的。在这个算法中,确实有一个隐含的假设,即在执行到当前步骤之前,max_left 通常小于或等于 max_right。这个假设是基于算法如何运行和如何更新这两个值的。

当算法比较 height[left - 1]height[right + 1] 时,它实际上是在确定下一步应该如何移动指针,以便有效地计算积水量。这里的关键逻辑是:

  1. 如果 height[left - 1] < height[right + 1]:

    • 这意味着左侧的边界较低。因此,我们可以假设 max_left(已经是左边的最高点)小于或等于 max_right
    • 在这种情况下,移动左指针是安全的,因为即使左边的墙较低,右边也有足够高的墙(max_right)来保持水。
  2. 如果 height[left - 1] >= height[right + 1]:

    • 这表明右侧的边界较低,或者至少与左边相等。
    • 因此,移动右指针是更安全的选择,因为左边有足够高的墙(max_left)来保持水。
  3. 更新 max_leftmax_right:

    • 在每一步中,max_leftmax_right 都会根据当前指针位置的情况进行更新。

总的来说,这个算法的核心在于它尝试从较低的一侧开始计算积水量,这样可以保证在那一侧的计算过程中,另一侧总是有足够高的边界来支撑水位。这种方法使得算法既高效又准确。

2 LC84. 柱状图中最大的矩形

在这里插入图片描述

2.1 解题

推荐题解:lc84 柱状图中的最大矩形

class Solution {// 时间复杂度进一步常数级优化public int largestRectangleArea(int[] heights) {int n=heights.length;int ans=0;int[]l=new int[n];int[]r=new int[n];Arrays.fill(r,n);Deque<Integer>st=new LinkedList<>();int i=0;while(i<n){while(!st.isEmpty()&&heights[st.peekLast()]>=heights[i]){r[st.pollLast()]=i;}l[i]=st.isEmpty()?-1:st.peekLast();st.addLast(i);i++;}for(i=0;i<n;i++){ans=Math.max(ans, (r[i]-l[i]-1)*heights[i]);}return ans;}// 时间复杂度优化public int largestRectangleArea4(int[] heights) {int n=heights.length;int ans=0;int[]l=new int[n];int[]r=new int[n];Deque<Integer>st=new LinkedList<>();int i=0;while(i<n){while(!st.isEmpty()&&heights[st.peekLast()]>=heights[i]){st.pollLast();}l[i]=st.isEmpty()?-1:st.peekLast();st.addLast(i);i++;}st.clear();i=n-1;while(i>=0){while(!st.isEmpty()&&heights[st.peekLast()]>=heights[i]){st.pollLast();}r[i]=st.isEmpty()?n:st.peekLast();st.addLast(i);i--;}for(i=0;i<n;i++){ans=Math.max(ans, (r[i]-l[i]-1)*heights[i]);}return ans;}public int largestRectangleArea3(int[] heights) {int n=heights.length;int ans=0;// 按高度遍历,这里的遍历的是数组中所有出现的高度for(int i=0;i<n;i++){int j=i;while(j>=0&&heights[j]>=heights[i]){j--;}int k=i;while(k<n&&heights[k]>=heights[i]){k++;}ans=Math.max(ans,(k-j-1)*heights[i]);}return ans;}public int largestRectangleArea2(int[] heights) {int n=heights.length;int ans=0;for(int i=0;i<n;i++){int min=Integer.MAX_VALUE;for(int j=i;j<n;j++){min=Math.min(min, heights[j]);ans=Math.max(ans,(j-i+1)*min);}}return ans;}
}

3 相似之处

   // lc84 柱状图中的最大矩形 : 时间复杂度优化public int largestRectangleArea4(int[] heights) {int n=heights.length;int ans=0;int[]l=new int[n];int[]r=new int[n];Deque<Integer>st=new LinkedList<>();int i=0;while(i<n){while(!st.isEmpty()&&heights[st.peekLast()]>=heights[i]){st.pollLast();}l[i]=st.isEmpty()?-1:st.peekLast();st.addLast(i);i++;}st.clear();i=n-1;while(i>=0){while(!st.isEmpty()&&heights[st.peekLast()]>=heights[i]){st.pollLast();}r[i]=st.isEmpty()?n:st.peekLast();st.addLast(i);i--;}for(i=0;i<n;i++){ans=Math.max(ans, (r[i]-l[i]-1)*heights[i]);}return ans;}// lc42 接雨水public int trap(int[] height) {int n=height.length;int ans=0;int lm=-1,rm=-1;int l=1,r=n-2;while(l<=r){if(height[l-1]<height[r+1]){lm=Math.max(lm,height[l-1]);if(lm>height[l]){ans+=lm-height[l];}l++;}else{rm=Math.max(rm,height[r+1]);if(rm>height[r]){ans+=rm-height[r];}r--;}}return ans;}

3.1 这两个题解很相似

确实,从您提供的 LeetCode 问题 42 (“接雨水”) 和 问题 84 (“柱状图中最大的矩形”) 的解题代码中,我们可以看到一些相似之处。尽管两个问题的实际目标不同,它们的解法都涉及到一些共同的编程技巧和思路。

相似点

  1. 单调栈的应用:

    • 在问题 84 的解法中,使用单调栈(Deque<Integer> st)来维护一个单调递增的高度索引,这有助于确定每个条形的左右边界。
    • 这种栈的应用在处理数组中的连续区间问题时非常有效,如寻找左右两侧第一个小于当前元素的位置。
  2. 双指针技术:

    • 在问题 42 的解法中,使用左右双指针(lr)来从两端向中间遍历数组。
    • 这种技术允许在一次遍历中比较和处理两端的元素,是处理此类问题的常见方法。
  3. 局部最大/最小值的更新:

    • 在问题 42 中,使用 lmrm(左侧和右侧的最大高度)来更新局部最大值,并用这些值来计算积水量。
    • 在问题 84 中,使用栈来计算每个条形的局部左右边界,再计算最大矩形面积。
  4. 动态计算过程中的条件判断:

    • 在问题 42 中,通过比较 height[l-1]height[r+1] 来决定是移动左指针还是右指针。
    • 在问题 84 中,通过比较 heights[st.peekLast()]heights[i] 来决定是继续在栈中添加新元素还是进行弹出操作。

不同点

虽然它们在某些解题技巧上相似,但这两个问题的实际应用是不同的:

  • 问题 42 (“接雨水”) 要求计算在不规则形状的容器中积累的雨水总量。
  • 问题 84 (“柱状图中最大的矩形”) 要求找出由不同高度的条形构成的最大矩形面积。

总体来说,虽然这两个问题的解法在某些编程技术上有交叉,但它们分别解决了不同的问题。这种技术上的相似性突显了数据结构和算法在解决不同问题时的通用性和灵活性。

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

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

相关文章

Git新建分支

修改代码之Git策略思考&#xff1a; 有三种办法&#xff1a; 需要在主分支上新建一个分支&#xff0c;不合并新建版本。其实也是先新建一个分支&#xff0c;然后合并到主分支&#xff0c;再删除分支。直接新建远程仓库。 考虑&#xff0c;3&#xff09;最浪费&#xff0c;其…

python爬取网站数据,作为后端数据

一. 内容简介 python爬取网站数据&#xff0c;作为后端数据 二. 软件环境 2.1vsCode 2.2Anaconda version: conda 22.9.0 2.3代码 链接&#xff1a; 三.主要流程 3.1 通过urllib请求网站 里面用的所有的包 ! pip install lxml ! pip install selenium ! pip install…

【JY】ANSYS Workbench在减隔震应用分析中的单元积分技术笔记

写在前文 尽管减隔震技术与有限元结合取得了众多成果&#xff0c;但仍面临诸多挑战&#xff0c;如材料非线性、模型不确定性等等。减隔震设计除了常规的宏观结构设计采用SAP2000、Etabs、Midas、SSG、Paco-SAP 或 YJK\PKPM等。 【JY】各类有限元软件计算功能赏析与探讨 我们需要…

navicat创建MySql定时任务

navicat创建MySql定时任务 前提 需要root用户权限 需要开启定时任务 1、开启定时任务 1.1 查看定时任务是否开启 mysql> show variables like event_scheduler;1.2 临时开启定时任务(下次重启后失效) set global event_scheduler on;1.3 设置永久开启定时任务 查看my…

《大话设计模式》让设计模式不再高深,连傻瓜都可以读得懂

写在前面 最近刚刚完成了设计模式的专栏的编写&#xff0c;其实关于设计模式的内容&#xff0c;早都想写点东西总结一下设计模式。为什么特别想写这方面东西&#xff0c;其中很大程度是受了《大话设计模式》这本书的影响&#xff0c;通过阅读这本书&#xff0c;我了解到了设计模…

优思学院|一文快速看懂TRIZ原理

在创新领域&#xff0c;TRIZ被翻译为发明问题的解决理论。TRIZ理论深刻揭示了创造发明的内在规律和原理&#xff0c;专注于澄清和强调系统中存在的矛盾&#xff0c;旨在完全解决这些矛盾&#xff0c;实现最终的理想解决方案。实践证明&#xff0c;运用TRIZ理论不仅能够极大地加…

如何搭建属于自己的AI数字人直播SAAS系统?

随着人工智能技术的不断发展&#xff0c;AI数字人直播正成为互联网行业的新宠。面向未来的AI数字人直播系统无疑是直播领域的新风口。虽然拥有众多优势&#xff0c;但从0到1搭建这个系统可能存在着资源、技术和时间的挑战。那么&#xff0c;如何可以快速搭建属于自己的AI数字人…

识别伪装IP的网络攻击方法

识别伪装IP的网络攻击可以通过以下几种方法&#xff1a; 观察IP地址的异常现象。攻击者在使用伪装IP地址进行攻击时&#xff0c;往往会存在一些异常现象&#xff0c;如突然出现的未知IP地址、异常的流量等。这些现象可能是攻击的痕迹&#xff0c;需要对此加以留意。 检查网络通…

《网络协议》06. HTTP 补充 · HTTPS · SSL/TLS

title: 《网络协议》06. HTTP 补充 HTTPS SSL/TLS date: 2022-10-06 18:09:55 updated: 2023-11-15 07:53:52 categories: 学习记录&#xff1a;网络协议 excerpt: HTTP/1.1 协议的不足、HTTP/2、HTTP/3、HTTP 协议的安全问题、SPDY、HTTPS、SSL/TLS、OpenSSL。 comments: fa…

Arm Cortex R52与TC3xx Aurix上下文切换对比

目录 1.Arm Cortex R52上下文切换 2.英飞凌TC3xx的CPU上下午切换 2.1 上下文类型 2.2 Task Switching Operation 2.3 Context管理寄存器 2.4 各种事件的上下文切换 2.4.1 中断/Trap的上下文切换 2.4.2 函数调用的上下文切换 2.4.3 FCALL/FRET的上下文切换 2.5 Contex…

【nlp】2.3 LSTM模型

LSTM模型 1 LSTM介绍2 LSTM的内部结构图2.1 LSTM结构分析2.2 Bi-LSTM介绍2.3 使用Pytorch构建LSTM模型2.4 LSTM优缺点1 LSTM介绍 LSTM(Long Short-Term Memory)也称长短时记忆结构, 它是传统RNN的变体,与经典RNN相比能够有效捕捉长序列之间的语义关联,缓解梯度消失或爆炸…

基于opencv的边缘检测方法

1、梯度运算 用OpenCV的形态变换&#xff08; 膨胀、腐蚀、开运算和闭运算&#xff09;函数morphologyEx 梯度运算即膨胀结果-腐蚀结果&#xff1a; 【注意】对于二值图像来说&#xff0c;必须是前景图像为白色&#xff0c;背景为黑色&#xff0c;否则需要进行反二值化处理 …