这是一道很经典的题目,问题如下:
题目地址
给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
解法1:动态规划
动态规划的核心就是将问题拆分成若干个子问题求解,那这道题的问题实际上可以拆分为求x轴上每一块区域能承载多少雨水,而x轴上每一块区域能承载多少雨水取决于它左边和右边的最大高度值以及它的自身高度,所以我们可以先维护两个数组leftMaxArr和rightMaxArr
// height是leetcode传进来的高度数组
leftMaxArr[i] = Math.max(leftMaxArr[i - 1], height)
rightMaxArr[i] = Math.min(rightMaxArr[i + 1], height)
leftMaxArr每一项对应着从左到当前区域高度的最大值,rightMaxArr就是从右到当前区域
知道了x轴上每一块区域的左右高度最大值后那这个子问题就很简单了,我们先定义一个子问题的结果数组result,那么公式可以这么写
result[i] = Math.max(leftMaxArr, rightMaxArr) - height[i]
最后将这个result数组的值加起来,就是本道题的结果了,当然,我们不需要使用到result数组,用一个变量维护即可,代码如下
/**
* @param {number[]} height
* @return {number}
*/
var trap = function(height) {
const len = height.length
const leftMaxArr = [], rightMaxArr = []
let result = 0
leftMaxArr[0] = height[0]
rightMaxArr[len - 1] = height[len - 1]
for (let i = 1; i < len - 1; i++) {
leftMaxArr[i] = Math.max(leftMaxArr[i - 1], height[i])
}
for (let i = len - 2; i > 0; i--) {
rightMaxArr[i] = Math.max(rightMaxArr[i + 1], height[i])
}
// 忽略 0 和 len - 1,因为它们是边界,不会有雨水
for (let i = 1; i < len - 1; i++) {
result += Math.min(leftMaxArr[i], rightMaxArr[i]) - height[i]
}
return result
};
解法2:双指针
这个解法是由解法一衍生而来,由解法一发现我们不需要同时知道左右的最大高度,只需要知道高度比较小的一侧,所以我们可以定义两个指针往中间收缩,每一次收缩的位置即为高度较小的一侧,这样就不用维护两个数组了,空间复杂度为O(1),代码如下:
/**
* @param {number[]} height
* @return {number}
*/
var trap = function(height) {
const len = height.length
let leftMax = height[0], rightMax = height[len - 1]
let result = 0
let left = 0, right = len - 1
while (left < right) {
// 此时leftMax一定小于rightMax,好比打擂台,强大的人可以一直打下去~
if (height[left] < height[right]) {
// 左侧为高度较小的一侧,对左指针当前位置用左侧最大高度进行计算
result += leftMax - height[left]
left++
// 更新左侧最大高度
leftMax = Math.max(leftMax, height[left])
} else {
result += rightMax - height[right]
right--
rightMax = Math.max(rightMax, height[right])
}
}
return result
}
这样这个问题就完美解决啦,leetcode还有一种关于单调栈的解法,有兴趣的可以了解下~