给定
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
题解1 暴力解法
思路
柱子与柱子之间能接多少水,就像上一题一样,取决于两边的高度。上一题是木板,这一题是柱子。
还和柱子本身的高度有关系,如果两边柱子之间的最矮那一边比柱子本身要高,那才会形成一个洼地,能够留住雨水。如果比柱子本身要矮,那么不会留下雨水。
我们可以在遍历的时候先找到他左边和右边最高的柱子,然后和柱子本身高度做比较。如果为正则累加到结果中。
为什么要找最高而不是简单看左右两边?因为可能形成横向的积水。
图中高度为 0 的柱子左右两边是 1,而形成了高度为 2的积水,这都是因为左右两侧更高的柱子形成了更高的雨水拦截。
代码
function trap(height: number[]): number {
const n: number = height.length;
let result: number = 0;
for (let i = 0; i < n; i++) {
let left_max: number = 0;
let right_max: number = 0;
for (let j = 0; j < i; j++) {
left_max = Math.max(left_max, height[j]);
}
for (let j = i + 1; j < n; j++) {
right_max = Math.max(right_max, height[j]);
}
const water: number = Math.min(left_max, right_max) - height[i];
if (water > 0) {
result += water;
}
}
return result;
};
时空复杂度分析
时间复杂度:两层循环,内部的两次循环只遍历了 n - 1次,所以渐进复杂度是 O(n^2)
空间复杂度:没有使用额外变量 O(1),内部两个 max 也是常数级
题解2 两次遍历
思路
有了暴力解法之后,我们很容易想到优化,就是把 left_max 和 right_max 缓存下来,不用每次都要在循环内部遍历计算。利用空间来换时间。
把 left_max 和 right_max 缓存为数组,最后计算总和即可。
代码
function trap(height: number[]): number {
const n: number = height.length;
const left_max: number[] = new Array(n).fill(0);
left_max[0] = height[0];
for (let i = 1; i < n; i++) {
left_max[i] = Math.max(height[i], left_max[i - 1]);
}
const right_max: number[] = new Array(n).fill(0);
right_max[n - 1] = height[n - 1];
for (let i = n - 2; i >= 0; i--) {
right_max[i] = Math.max(height[i], right_max[i + 1]);
}
let result: number = 0;
for (let i = 0; i < n; i++) {
// 为什么不用判断积水是否大于 `0`?
// 因为在遍历的过程中,包含该柱子的高度,左右最矮也是自己
// 即 Math.min(left_max[i], right_max[i]) >= height[i]
result += Math.min(left_max[i], right_max[i]) - height[i];
}
return result;
};
时空复杂度分析
时间复杂度:3次循环,每次 O(n), 所以总的时间复杂度为 O(n)
空间复杂度:使用了2个额外数组去保存,空间复杂度开销也是 O(n)
题解3 双指针解法
上面的代码已经在时间复杂度上寻得最优解,现在还剩下优化空间复杂度。我们可以考虑一下,只在计算积水量的时候需要用到 left_max 和 right_max。
是不是我们可以在遍历的过程中更新 left_max 和 right_max 并计算积水量呢?
答案是可以的。
代码
function trap(height: number[]): number {
let left: number = 0;
let right: number = height.length - 1;
let left_max: number = 0;
let right_max: number = 0;
let result: number = 0;
while (left < right) {
if (height[left] < height[right]) {
// 如果当前柱子高度小于等于left_max,累加水量;否则更新left_max
if (height[left] >= left_max) {
left_max = height[left];
} else {
result += left_max - height[left];
}
left += 1;
} else {
// 如果当前柱子高度小于等于right_max,累加水量;否则更新right_max
if (height[right] >= right_max) {
right_max = height[right];
} else {
result += right_max - height[right];
}
right -= 1;
}
}
return result;
};
时空复杂度分析
时间复杂度:O(n)
空间复杂度:O(1)