42. 接雨水 (hard)
给定 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
-
思路:首先考虑暴力做法,找找思路,暴力做法可以遍历数组,在每个位置分别往两边寻找左柱子中的最大高度和右柱子中的最大高度,找到之后,用左右最大高度的较小者减去当前柱子的高度,就是当前位置能接的水量。该方法要循环整个数组,并且每个位置要遍历数组寻找左右柱子高度的最大值,嵌套了一层循环,所以复杂度是
O(n^2)
。我们怎样加速嵌套的这层循环呢,其实可以预先计算从左往右和从右往左的最大高度数组,在循环数组的时候,可以直接拿到该位置左右两边的最大高度,当前位置的接水量就是左右两边高度的较小者减去当前位置柱子的高度
-
复杂度:时间复杂度
O(n)
,寻找左右的最大高度,循环计算每个位置的接水量,总共3个循环,但他们不是嵌套关系。空间复杂度是O(n)
,n是heights
数组,用到了leftMax
和rightMax
数组,即存放左右两边最大高度的数组。
方法1.动态规划
js:
var trap = function(height) {
const n = height.length;
if (n == 0) {
//极端情况
return 0;
}
const leftMax = new Array(n).fill(0);//初始化从左往右看的最大值数组
leftMax[0] = height[0];
for (let i = 1; i < n; ++i) {
leftMax[i] = Math.max(leftMax[i - 1], height[i]);
}
const rightMax = new Array(n).fill(0);//初始化从右往左看的最大值数组
rightMax[n - 1] = height[n - 1];
for (let i = n - 2; i >= 0; --i) {
rightMax[i] = Math.max(rightMax[i + 1], height[i]);
}
let ans = 0;
//循环数组,每个位置能接的雨水量就是这个位置左右最大值的较小者减去当前的高度
for (let i = 0; i < n; ++i) {
ans += Math.min(leftMax[i], rightMax[i]) - height[i];
}
return ans;
};
方法2:单调栈
- 思路:遍历
heights
数组,将其中的元素加入单调递减栈,如果当前柱子的高度大于栈顶柱子的高度, 不断出栈,相当于找到左边比当前柱子矮的位置,然后每次出栈之后都要累加一下面积。 - 复杂度:时间复杂度
O(n)
,n是heights的长度,数组中的每个元素最多入栈出栈一次。空间复杂度O(n)
,栈的空间,最多不会超过heights
的长度
js:
var trap = function(height) {
let ans = 0;
const stack = [];//单调递减栈。存放的是下标哦
const n = height.length;
for (let i = 0; i < n; ++i) {
//循环heights
//当前柱子的高度大于栈顶柱子的 不断出栈
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 currWidth = i - left - 1;//计算宽度
const currHeight = Math.min(height[left], height[i]) - height[top];//计算高度
ans += currWidth * currHeight;//计算当面积
}
stack.push(i);//加入栈
}
return ans;
};