题目
给定 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] <= 105
思路1:正反扫描
可以使用动态规划来预处理左右两侧的最大高度,使得每次查询的时间复杂度为 O(1)。 具体来说,我们可以分别使用两个数组 leftMax 和 rightMax 来记录每个位置左侧和右侧的最大高度。然后我们可以遍历一遍数组,分别计算每个位置的积水量,最后将所有的积水量加起来就是答案。
function trap(height: number[]): number {
const len = height.length;
if(!len) return 0;
const maxL = [];
const maxR = [];
// 两端初始值
maxL[0] = height[0];
maxR[len - 1] = height[len - 1];
// 扫描左边最大值
for(let i = 1; i < len; i++) {
maxL[i] = Math.max(maxL[i - 1], height[i])
}
// 扫描右边最大值
for(let i = len - 1 - 1; i >= 0; i--) {
maxR[i] = Math.max(height[i], maxR[i + 1])
}
// 取左右最小值 - 当前高度
let res = 0;
for(let i = 0; i < len; i++) {
res += Math.min(maxL[i], maxR[i]) - height[i];
}
return res;
};
- 时间复杂度为 O(n)
- 空间复杂度为 O(n)。
图解参考:michael.blog.csdn.net/article/det…
思路2:双指针
这道题目可以使用双指针的思想来解决。具体来说,我们可以维护两个指针 left 和 right,分别从两端开始向中间遍历。在遍历的过程中,我们需要维护两个变量 leftMax 和 rightMax,分别表示 left 左侧和 right 右侧的最大高度。如果 height[l] < height[r],说明此时 left 可以积水,积水的高度为 leftMax - height[left]。反之,说明此时 right 可以积水,积水的高度为 rightMax - height[right]。遍历完成后,所有的积水量之和就是答案。
function trap(height: number[]): number {
const len = height.length;
if(!len) return 0;
// 双指针法
let l = 0, r = len - 1;
let maxL = 0, maxR = 0, res = 0;
while(l < r) {
if(height[l] < height[r]) {
// 右侧有高墙
height[l] >= maxL ? (maxL = height[l]) : res += maxL - height[l];
l++
} else {
// 左侧有高墙
height[r] >= maxR ? (maxR = height[r]) : res += maxR - height[r];
r--
}
}
return res;
};
- 时间复杂度 这道题目的时间复杂度为 O(n),其中 n 是数组的长度。需要遍历数组中的每个元素一次,并且每个元素最多被访问两次,因此时间复杂度为 O(n)。
- 这道题目是一个非常经典的题目,需要使用双指针的思想来解决。在实现过程中需要注意边界条件的处理,以及对左右两侧最大高度的维护。
思路3:递减栈
我们可以使用栈来维护当前的递减序列。具体来说,我们遍历每个位置,如果当前位置的高度小于等于栈顶元素的高度,说明这个位置不能形成凹槽,直接将这个位置入栈;否则,我们需要计算当前位置能够形成的凹槽的面积,并更新答案。
function trap(height: number[]): number {
const len = height.length;
let res = 0;
if(!len) return 0;
// 单调递减栈
const stack = [];
for(let i = 0; i < len; i++) {
// 栈存在,且当前元素大于栈顶元素
while(stack.length && height[i] > height[stack[stack.length - 1]]) {
const top = stack.pop();
if(!stack.length) break;
const left = stack[stack.length - 1];
const width = i - left - 1;// 中间同样高度的几个
console.log(width)
const diff = Math.min(height[left], height[i]) - height[top];// 两边最小水高 - 容器高
res += width * diff;
}
stack.push(i);// 向下的斜坡\,会一直递减。一旦不递减,就进入出栈的流程。
}
return res;
};
- 时间复杂度为 O(n)
- 空间复杂度为 O(n)