接雨水
题目链接: https://leetcode.cn/problems/trapping-rain-water/
# LeetCode 42. 接雨水
# 题目描述
给定 n
个非负整数表示每个宽度为 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 个单位的雨水(蓝色部分表示雨水)。
# 思路解析
采用逐列计算,计算每列最多能接雨水量,然后把每列能接的雨水量加起来,就可以得到所有柱子总的接雨水量。
那么对于第i
列,要怎样计算它能接的雨水量呢?
我们可以找出0 ~ i
列最高的柱子,记为maxL
,然后找到i ~ n
列最高的柱子,记为maxR
,maxL
和maxR
分别是第i
列的左右边界,因为第i
列能接的雨水量是由较短的那个边界决定的,所以min(maxL, maxR) - height[i]
就是第i
列能接的雨水量,下图中的红色区域就是第i
列可以接的雨水量。
我们以上面的数组height = [0,1,0,2,1,0,1,3,2,1,2,1]
为例,来看一下整个接雨水的过程。
- 从左到右遍历数组,来计算
0 ~ i
列最高的柱子maxL
,即第i
列的左边界。
height | 0 | 1 | 0 | 2 | 1 | 0 | 1 | 3 | 2 | 1 | 2 | 1 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
maxL | 0 | 1 | 1 | 2 | 2 | 2 | 2 | 3 | 3 | 3 | 3 | 3 |
- 从右到左遍历数组, 来计算
n ~ i
列最高的柱子maxR
,即第i
列的右边界。。
height | 0 | 1 | 0 | 2 | 1 | 0 | 1 | 3 | 2 | 1 | 2 | 1 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
maxL | 0 | 1 | 1 | 2 | 2 | 2 | 2 | 3 | 3 | 3 | 3 | 3 |
maxR | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 2 | 2 | 2 | 1 |
- 计算
min(maxL, maxR)
,即较短的边界长度。
height | 0 | 1 | 0 | 2 | 1 | 0 | 1 | 3 | 2 | 1 | 2 | 1 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
maxL | 0 | 1 | 1 | 2 | 2 | 2 | 2 | 3 | 3 | 3 | 3 | 3 |
maxR | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 2 | 2 | 2 | 1 |
min(maxL, maxR) | 0 | 1 | 1 | 2 | 2 | 2 | 2 | 3 | 2 | 2 | 2 | 1 |
- 计算每一列能接的雨水量
min(maxL, maxR) - height[i]
,即较短的边界减去第i
列柱子本身的高度。
height | 0 | 1 | 0 | 2 | 1 | 0 | 1 | 3 | 2 | 1 | 2 | 1 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
maxL | 0 | 1 | 1 | 2 | 2 | 2 | 2 | 3 | 3 | 3 | 3 | 3 |
maxR | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 2 | 2 | 2 | 1 |
min(maxL, maxR) | 0 | 1 | 1 | 2 | 2 | 2 | 2 | 3 | 2 | 2 | 2 | 1 |
min(maxL, maxR)-height[i] | 0 | 0 | 1 | 0 | 1 | 2 | 1 | 0 | 0 | 1 | 0 | 0 |
- 把所有的
min(maxL, maxR)-height[i]>0
的值加在一起就是能接雨水的总量。
上面的整个过程需要借助额外的空间,空间复杂度为O(n),其中n
为数组中元素的个数。
我们可以使用双指针法对上面的步骤进行优化。
定义L
指向数组的第一个元素,R
指向数组的最后一个元素,初始化左右边界maxL = height[L], maxR = height[R]
。
因为每一列接到雨水的量是由较短的边界决定的,所以一旦某一列左右边界的较短边界确定了,我们就可以计算对应列可接的雨水量,每次计算左右边界maxL
和maxR
中较小值对应的列就可以达到这个目的。
如果
maxL < maxR
,此时L
对应列的较短边界是确定的,利用公式maxL - height[L]
计算L
对应列可以接到雨水的量,大于0
就累加到结果中,并将L
向右移动一步++L
,更新maxL = max(maxL, height[L])
。如果
maxL >= maxR
,此时R
对应列的较短边界是确定的,利用公式maxR - height[R]
计算R
对应列可以接到雨水的量,大于0
就累加到结果中,并将R
向左移动一步--R
,更新maxR = max(maxR, height[R])
。
# c++代码
class Solution {
public:
int trap(vector<int>& height) {
int res = 0;
//定义两个指针
int left = 0, right = height.size() - 1;
//定义待计算列的左右边界
int maxL = height[left], maxR = height[right];
while (left <= right) {
//计算较小边界对应指针指向的列接雨水的量
if (maxL < maxR) {
int temp = maxL - height[left];
if (temp > 0)
res += temp;
++left;
//防止越界访问
if (left < height.size())
maxL = max(maxL, height[left]);
} else {
int temp = maxR - height[right];
if (temp > 0)
res += temp;
--right;
//防止越界访问
if (right >= 0)
maxR = max(maxR, height[right]);
}
}
return res;
}
};
# java代码
class Solution {
public int trap(int[] height) {
int res = 0;
//定义两个指针
int left = 0, right = height.length - 1;
//定义待计算列的左右边界
int maxL = height[left], maxR = height[right];
while (left <= right) {
//计算较小边界对应指针指向的列接雨水的量
if (maxL < maxR) {
int temp = maxL - height[left];
if (temp > 0) {
res += temp;
}
left++;
//防止越界访问
if (left < height.length) {
maxL = Math.max(maxL, height[left]);
}
} else {
int temp = maxR - height[right];
if (temp > 0) {
res += temp;
}
right--;
//防止越界访问
if (right >= 0) {
maxR = Math.max(maxR, height[right]);
}
}
}
return res;
}
}
# python代码
class Solution:
def trap(self, height: List[int]) -> int:
res = 0
#定义两个指针
left, right = 0, len(height) - 1
#定义待计算列的左右边界
maxL, maxR = height[left], height[right]
while left <= right:
#计算较小边界对应指针指向的列接雨水的量
if maxL < maxR:
temp = maxL - height[left]
if temp > 0:
res += temp
left += 1
#防止越界访问
if left < len(height):
maxL = max(maxL, height[left])
else:
temp = maxR - height[right]
if temp > 0:
res += temp
right -= 1
#防止越界访问
if right >= 0:
maxR = max(maxR, height[right])
return res
# 复杂度分析
时间复杂度: 只需要遍历一遍数组,所以时间复杂度为O(n),其中n
为数组的大小。
空间复杂度: 只用到两个指针,所以空间复杂度为O(1)。