接雨水
注意和盛最多水的容器区别。
盛最多水的容器
接雨水
1、题目
原题链接
给定 n 个非负整数
表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
上面是由数组 [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、思路
(1)明确对于位置 i,能装下多少水呢?
根据木桶原理(三维层次,这里简化成二维平面),受制于左右两侧木板的高度,并且取决于高度较低的一侧,这很好理解。即:
- 位置 i 能达到的水柱高度和其左边的最高柱子、右边的最高柱子有关
- 两个柱子高度为
l_max 和 r_max
;位置 i
最大的水柱高度就是min(l_max, r_max)
water[i] = min(
# 左边最高的柱子
max(height[0..i]),
# 右边最高的柱子
max(height[i..end])
) - height[i]
3、题解
3.1、暴力解法
int trap(vector<int>& height) {
int n = height.size();
int ans = 0;
for (int i = 1; i < n - 1; i++) {
int l_max = 0, r_max = 0;
// 找右边最高的柱子
for (int j = i; j < n; j++)
r_max = max(r_max, height[j]);
// 找左边最高的柱子
for (int j = i; j >= 0; j--)
l_max = max(l_max, height[j]);
// 如果自己就是最高的话,
// l_max == r_max == height[i]
ans += min(l_max, r_max) - height[i];
}
return ans;
}
时间复杂度 O(N^2),空间复杂度 O(1)
3.2、空间换时间(备忘录优化)
之前的暴力解法,不是在每个位置 i 都要计算 r_max 和 l_max
吗?
我们开两个数组 r_max 和 l_max
充当备忘录,l_max[i] 表示位置 i 左边最高的柱子高度
,r_max[i] 表示位置 i 右边最高的柱子高度
。预先把这两个数组计算好,避免重复计算
。
int trap(vector<int>& height) {
if (height.empty()) return 0;
int n = height.size();
int ans = 0;
// 数组充当备忘录
vector<int> l_max(n), r_max(n);
// 初始化 base case
l_max[0] = height[0];
r_max[n - 1] = height[n - 1];
// 从左向右计算 l_max
for (int i = 1; i < n; i++)
l_max[i] = max(height[i], l_max[i - 1]);
// 从右向左计算 r_max
for (int i = n - 2; i >= 0; i--)
r_max[i] = max(height[i], r_max[i + 1]);
// 计算答案
for (int i = 1; i < n - 1; i++)
ans += min(l_max[i], r_max[i]) - height[i];
return ans;
}
时间复杂度降低为 O(N),已经是最优了,但是空间复杂度是 O(N)
3.3、双指针解法
之前的暴力解法
和备忘录解法
,原理如下:
双指针解法
中,l_max 和 r_max
代表的是 height[0..left]
和 height[right..end]
的最高柱子高度。
//l_area左侧高度记录,对r_area 同理
if (l_area < r_area ) {
ans += l_area - height[left];
left++;
}
此时的 l_max 是 left 指针左边的最高柱子
,但是 r_max 并不一定是 left 指针右边最高的柱子
,这真的可以得到正确答案吗?
其实这个问题要这么思考,我们只在乎 min(l_max, r_max)
。对于上图的情况,我们已经知道 l_area < r_area
了,至于这个 r_max
是不是右边最大的,不重要,重要的是 height[i] 能够装的水只和 l_max 有关
。
l_max < r_max
,解读为,右边已经有一个靠山给我们撑着了,并且这个靠山比左侧的靠山还要靠谱,我们就别纠结右侧靠山是不是有更靠谱的了(更高的),并且你要清晰的看到,左侧l_area = max(l_area,height[left]);
l_area
一直是左侧最高的,所以更不要担心左侧会漏水
class Solution {
public:
int trap(vector<int>& height) {
int len = height.size();
if(len < 3)//最少三块才能有聚水的可能
return 0;
int l_area = height[0],r_area = height[len - 1];
int ans = 0;
int left = 0,right = len - 1;
while(left <= right)
{
l_area = max(l_area,height[left]);
r_area = max(r_area,height[right]);
if(l_area < r_area)
{
ans += l_area - height[left];
left++;
}
else
{
ans += r_area - height[right];
right--;
}
}
return ans;
}
};
参考
https://labuladong.gitbook.io/algo/gao-pin-mian-shi-xi-lie/jie-yu-shui