给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例 1:
输入: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 个单位的雨水(蓝色部分表示雨水)。
示例 2:
输入:height = [4,2,0,3,2,5]
输出:9
提示:
n == height.length
1 <= n <= 2 * 104
0 <= height[i] <= 10
解法1(会因为时间复杂度原因导致失败)
思路,一层一层进行遍历
红色区域中的水,数组是 height = [ 0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1 ] 。
原则是高度小于 1,temp ++,高度大于等于 1,ans = ans + temp,temp = 0。
temp 初始化为 0,ans = 0
height[0] 等于 0 < 1,不更新。
height[1] 等于 1 >= 1,开始更新 temp。
height[2] 等于 0 < 1,temp = temp + 1 = 1。
height[3] 等于 2 >= 1,ans = ans + temp = 1,temp = 0。
height[4] 等于 1 >= 1,ans = ans + temp = 1,temp = 0。
height[5] 等于 0 < 1,temp = temp + 1 = 1。
height[6] 等于 1 >= 1,ans = ans + temp = 2,temp = 0。
剩下的 height[7] 到最后,高度都大于等于 1,更新 ans = ans + temp = 2,temp = 0。而其实 temp 一直都是 0,所以 ans 没有变化。
public int trap(int[] height) {
// 如果输入的height为空或者长度为0,直接返回0,没有可以存水的地方
if (height == null || height.length == 0) return 0;
// 找到数组中最高的高度,作为外层循环的上限
int max = Arrays.stream(height).max().getAsInt();
int len = height.length;
int ans = 0;
// 外层循环,从高度1遍历到最高高度max,逐层处理
for (int i = 1; i <= max; i++) {
// 标记是否开始遇到低于当前层高度i的有效区域,初始化为false
boolean isStart = false;
int tmp = 0; // 临时记录当前层i下,两个有效边界之间的低于i的高度的格子数
// 内层循环,遍历整个height数组
for (int j = 0; j < len; j++) {
// 如果当前位置的高度大于等于当前层高度i,说明遇到了一个有效边界
if (height[j] >= i) {
// 将临时记录的tmp加到结果ans中,因为当前遇到了新的边界,中间的tmp就是这两个边界之间的存水量
ans = ans + tmp;
tmp = 0; // 重置临时变量
isStart = true; // 标记已经开始遇到有效边界
}
// 如果当前位置的高度小于当前层高度i,并且已经开始遇到有效边界(isStart为true),说明处于两个有效边界之间的区域
if (height[j] < i && isStart) {
tmp++; // 临时记录这个低于i的格子数,用于后续遇到下一个边界时计算存水量
}
}
}
return ans;
}
注意:
1.当你把tmp的初始化,置于方法外面的时候,会导致一些错误出现
2.为什么要设置isStart呢?
为了判断第一次遇见左边界的时候,才会真正增加雨水格子。
3.为什么没有右边界的判断呢?
因为判断height[j] >= i的时候,就相当于判断两次边界了,比如当我是[ 0 , 1 , 0 , 1 , 0 ]的时候
最后一次1,就会因为height [j] >= i 而判断成边界,并且把tmp进行加入,同时置为0。
解法二(动态规划)
public int trap(int[] height) {
// 获取数组的长度
int len = height.length;
// 用于存储最终的雨水总量
int ans = 0;
// 左边数组,leftArr[i]表示从数组起始位置到i位置的最高高度
int[] leftArr = new int[len];
// 右边数组,rightArr[i]表示从数组末尾位置到i位置的最高高度
int[] rightArr = new int[len];
// 初始化左边数组的第一个元素,因为它左边没有元素,所以自身就是最高高度
leftArr[0] = height[0];
// 从左到右遍历,填充左边数组
for (int i = 1; i < len; i++) {
// 当前位置的左边最高高度是左边数组前一个位置的最高高度和当前位置高度的较大值
leftArr[i] = Math.max(leftArr[i - 1], height[i]);
}
// 初始化右边数组的最后一个元素,因为它右边没有元素,所以自身就是最高高度
rightArr[len - 1] = height[len - 1];
// 从右到左遍历,填充右边数组
for (int j = len - 2; j >= 0; j--) {
// 当前位置的右边最高高度是右边数组后一个位置的最高高度和当前位置高度的较大值
rightArr[j] = Math.max(rightArr[j + 1], height[j]);
}
// 遍历整个数组,计算每个位置的存水量并累加
for (int i = 0; i < len; i++) {
// 当前位置的存水量等于左边最高和右边最高的较小值减去当前位置的高度
ans += Math.min(leftArr[i], rightArr[i]) - height[i];
}
// 返回总的存水量
return ans;
}
说明:
1. 首先创建了两个辅助数组leftArr和rightArr,分别用来存储每个位置左边的最高高度和右边的最高高度。
2. 遍历填充leftArr数组:从左到右,每个位置i的左边最高高度是前一个位置i-1的左边最高高度和当前位置高度的较大值。
3. 遍历填充rightArr数组:从右到左,每个位置j的右边最高高度是后一个位置j+1的右边最高高度和当前位置高度的较大值。
4. 最后遍历原数组,对于每个位置i,其能存储的雨水量为左边最高高度和右边最高高度的较小值减去当前位置的高度,将所有位置的存水量累加起来就是最终的结果。这种方法利用了预处理的左右最高高度数组,将时间复杂度优化到了O(n),空间复杂度也是O(n)。
图片作者:力扣官方题解
链接:https://leetcode.cn/problems/trapping-rain-water/solutions/692342/jie-yu-shui-by-leetcode-solution-tuvc/
来源:力扣(LeetCode)