第一篇 LeetCode(42)接雨水

news/2024/10/6 20:29:21

LeetCode(42)接雨水

力扣官网

题目描述:给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

img

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。

基本思路:

  1. 基础解题思路:看到这道题时,应该要能够想到要遍历数组,关键是要如何遍历

遍历方法通常情况下有:单指针(从左往右或者从右往左或者二者结合),双指针(一个从左往右遍历,一个从右往左遍历),’二者结合‘的意思区别于双指针,’二者结合‘意思是一道题目里有一次遍历是从起点遍历到终点,还有一次遍历是从终点遍历到起点

  1. 那要如何统计雨水量呢?不难想到是要边遍历边统计,且是要用一个数字去减当前遍历到的柱子高度,为什么?总雨水量是柱子间能够承接的雨水量的累加和。判断柱子间能够接多少水,注意我这里用到的词是柱子间,间!!所以遍历到第i个柱子的高度(height)时,我们要用一个数字去减遍历到的第i个柱子的高度height[i]。
  2. 如何去确定这个数字是什么,是个难点!

题解:

方法一:动态规划

fig1

有没有一点思路了?用到了一个我们刚刚说的哪个遍历方法?没错,用到的是单指针,且是二者结合的方式,至于为什么,下面会说,先保留。

  1. 第一次遍历:从左往右,每次遍历到一个值时,要做的是收集遍历到当前节点时遍历过程中的最大值,即官方给的代码中的leftMax[i] = Math.max(leftMax[i - 1], height[i]);

  2. 第二次遍历:从右往左,每次遍历到一个值时,要做的是收集遍历到当前节点时遍历过程中的最大值,即官方给的代码中的rightMax[i] = Math.max(rightMax[i + 1], height[i]);

  3. 还有一次遍历,结合刚刚收集到的leftMax数组,rightMax数组,以及height数组,遍历次数是height数组的长度,每遍历到一个i,要做的是统计雨水量了,对ans进行累加了!如何累加?每遍历到一个i,都会有一个height[i],一个leftMax[i],以及一个rightMax[i],通过刚刚第一点的分析,相信已经了解到了leftMax数组的第i个元素的含义,含义是什么,是从左往右遍历height数组时,从起点到第i个元素的height数组中的最大值。rightMax数组同理。

  4. 判断柱子间能够接多少水,用的是leftMax[i]与rightMax[i]中的最小值去剪掉height[i],要理解用的这个数字是leftMax[i]与rightMax[i]中的最小值。如何理解,一个很好的方法就是实践!结合上面的图解+height数组,相信你能够理解了吧。因为是承接的水量是由两个柱子中最小的那根柱子来决定的(木桶效应)。

  5. 为什么叫动态规划,leftMax[i] = Math.max(leftMax[i - 1], height[i]);rightMax[i] = Math.max(rightMax[i + 1], height[i]);发现什么规律没有,,leftMax[i]与rightMax[i]值的确定都跟leftMax[i - 1]跟(rightMax[i + 1]有关系,所以简单理解,就是第i个值的确定与第i-1个值有关系,即为动态规划,当然,动态规划不是这个定义,我概括的比较粗糙。

    img

class Solution {public int trap(int[] height) {int n = height.length;if (n == 0) {return 0;}int[] leftMax = new int[n];leftMax[0] = height[0];for (int i = 1; i < n; ++i) {leftMax[i] = Math.max(leftMax[i - 1], height[i]);}int[] rightMax = new int[n];rightMax[n - 1] = height[n - 1];for (int i = n - 2; i >= 0; --i) {rightMax[i] = Math.max(rightMax[i + 1], height[i]);}int ans = 0;for (int i = 0; i < n; ++i) {ans += Math.min(leftMax[i], rightMax[i]) - height[i];}return ans;}
}作者:力扣官方题解

方法二:单调栈

  1. 该方法同样是使用单指针的方法,但是只用了一次遍历,从左往右遍历height数组。
  2. 那么如何处理每一次遍历到的height[i]呢?入栈。入栈的数据是height数组的下标(0,1,2,3....),且并不是一开始就入栈,是要先处理一些情况,期间肯定包括怎么得到柱子间的雨水量。设计一种模式,只要栈不为空且遍历到的元素height[i]比栈顶元素大(’只要‘ 所以采用while循环),我们就删掉栈顶元素,这样子在每次遍历height数组前,栈都满足栈顶元素比上一个进栈的元素小(height[i]比栈顶元素小,不删栈顶元素,仅仅把height[i]放入栈中)。那么在遍历到height[i]比栈顶元素大时,此时就满足了两边(栈顶的上一个元素+遍历到的元素)高,中间(栈顶)低的情况了,用两边中最小的数字减掉中间(栈顶)数字,就可以得到currHeight了,再使用i - left - 1得到curWidth。二者相乘即可得到一个大的区间内雨水的承接量,注意,得到总雨水承接两要用累加。注意,这个过程要使用while操作。原因是只要满足两边高,中间低的情况,我们都要收集这个区间内的雨水承接量。while循环结束后,把下标放进栈中。
class Solution {public int trap(int[] height) {int n = height.length;if (n == 0) {return 0;}int[] leftMax = new int[n];leftMax[0] = height[0];for (int i = 1; i < n; ++i) {leftMax[i] = Math.max(leftMax[i - 1], height[i]);}int[] rightMax = new int[n];rightMax[n - 1] = height[n - 1];for (int i = n - 2; i >= 0; --i) {rightMax[i] = Math.max(rightMax[i + 1], height[i]);}int ans = 0;for (int i = 0; i < n; ++i) {ans += Math.min(leftMax[i], rightMax[i]) - height[i];}return ans;}
}作者:力扣官方题解

方法三:双指针

  1. 该方法用到的是双指针。
  2. 左指针遍历到height数组的每一个元素,更新遍历到的元素中的最大值leftMax。右指针遍历到height数组的每一个元素,更新遍历到的元素中的最大值rightMax。
  3. 两种情况,如果左指针遍历到的元素比右指针遍历到的元素小height[left] < height[right],那么就要用leftMax - height[left]得到两根柱子间的雨水承接量,同时左指针右移。这里要说明的是,为什么被减数是leftMax,因为在height[left] < height[right]情况下,肯定也满足了leftMax<rightMax,前面两种方法里,都涉及到了’两边高,中间低,用两边最小的数字减去中间的数字‘,这里同理。不一样的地方在于这里的两边不是中间柱子的左右两根柱子。建议用具体的height数组自己操演一遍,加深理解。
  4. 右指针同理。
class Solution {public int trap(int[] height) {int ans = 0;int left = 0, right = height.length - 1;int leftMax = 0, rightMax = 0;while (left < right) {leftMax = Math.max(leftMax, height[left]);rightMax = Math.max(rightMax, height[right]);if (height[left] < height[right]) {ans += leftMax - height[left];++left;} else {ans += rightMax - height[right];--right;}}return ans;}
}作者:力扣官方题解

以上,是参考力扣官方解析,与自己的见解,如有错误,欢迎指正。

题外话:我觉得理解代码最好的方式是:自己举个例子,结合代码操演一遍,两遍,三遍........学习路上,希望你能发现java的魅力,并爱上java。

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

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

相关文章

【算法学习】圆方树——处理仙人掌的利器

圆方树大概分两种,一个是圆方树,一个是广义圆方树。 圆方树 这可以解决仙人掌上的问题。任意一条边至多只出现在一条简单回路的无向连通图称为仙人掌。很多题解将其作为无向图构建,本文将其构建为外向树,在这个问题中两种构建方式不会影响求解。 构建方式 记读入的图为原图…

kettle从入门到精通 第六十七课 ETL之kettle 再谈kettle阻塞,阻塞多个分支的多个步骤

场景:ETL沟通交流群内有小伙伴反馈,如何多个分支处理完毕之后记录下同步结果呢?或者是调用后续步骤、存储过程、三方接口等。 解决:使用步骤Blocking step进行阻塞处理即可。1、 如下流程图中利用Blocking step步骤同时阻塞【模拟表输出1】和【模拟表输出2】两个步骤,只有…

高考假+端午 集训

相当不充实的集训6.5 whk ? 水 本来是今天开始集训的来着 但是要去看牙,所以能多待一天 🥰 一年了,终于把被我妈爆破的电脑整好了 (原因是更新没空间了) 但是重置前让我体验一下米哈游的新启动器,我就下了 整完后把原下回来发现新启动器没了,恼了( 早上睡得晚了,我爸…

如何查看网络连接人数?为你介绍三种方法

方法一:通过命令提示符查看1. 打开命令提示符(Windows键+R键,输入cmd并回车)。2. 在命令提示符窗口中输入“netstat -an”命令,并按回车键执行。3. 观察输出的信息,找到本地地址和外部地址对应的TCP和UDP连接数。4. 根据连接数可以大致判断当前网络连接的人数。方法二:通…

SpringBoot 使用 Zookeeper 实现分布式锁

之前的博客介绍过 zookeeper 的分布式锁,只不过是基于 Spring 的实现(技术太老了),现在肯定使用 SpringBoot 进行实现,因此有必要再写一篇博客。 有关 zookeeper 的部署,以及分布式锁细节,这里不再赘述,可以访问我之前编写的博客。 zookeeper 的单机和集群部署:https:…

openwrt扩容相关

硬件:R2S 固件:openwrt(QiuSimons/YAOF) A:最近给R2S换了个带Docker的固件,目前大多数固件刷完之后都会多出一堆空闲空间。如何把这些空间给overlay和/opt/docker扩容? Q:系统->启动项->本地启动脚本 在 exit 0 前面加入脚本强行挂载mount /dev/mmcblk0p3 /opt/d…

LeetCode 974 Subarray Sums Divisible by K All In One

LeetCode 974 Subarray Sums Divisible by K All In One LeetCode 974 能被 K 整除的子数组之和LeetCode 974 Subarray Sums Divisible by K All In OneLeetCode 974 能被 K 整除的子数组之和errosfunction subarraysDivByK(nums: number[], k: number): number {// -5 / 0 / 5…

ansible高级操作 serial滚动更新

1.异步操作和轮询 默认情况下,剧本中的任务会一直处于打开状态,直到任务在每个节点上完成。这样可以会造成阻塞和超时,因此我们可以使用异步模式一次运行所有任务,然后轮询直到它们完成为止。Ansible本身就是采用的多线程来操作多个主机节点,可以使用-P来异步操作。现在所…