题目描述
原题链接:直方图的水量
给定一个直方图(也称柱状图),假设有人从上面源源不断地倒水,最后直方图能存多少水量?直方图的宽度为 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
解答
好家伙,但凡跟动态规划沾点边都能上困难难度啊!
解法一
先找出最大的数,以它为界分为两半,从边缘遍历到最高点,遍历过程中记录下遍历过程中的次高点,小于次高点时为结果加上次高点和当前数的差值,大于或等于次高点时更新次高点。
/**
* @param {number[]} height
* @return {number}
*/
var trap = function (height) {
// 找到最大数所在的索引
const maxIndex = findMaxIndex(height)
// 从左边缘遍历到最大数能接的水
const leftResult = compute(height.slice(0, maxIndex))
// 从右边缘遍历到最大数能接的水
const rightResult = compute(height.slice(maxIndex).reverse())
return leftResult + rightResult
}
var findMaxIndex = function (arr) {
var max = -Infinity
var maxIndex = -1
var index = 0
while (index < arr.length) {
if (arr[index] > max) {
max = arr[index]
maxIndex = index
}
index++
}
return maxIndex
}
var compute = function (height) {
let index = 0,
result = 0,
curHeight = 0
// 去除边缘的 0
while (height[index] === 0) {
index++
}
curHeight = height[index]
while (++index < height.length) {
if (height[index] >= curHeight) {
// 大于或等于次高点时更新次高点
curHeight = height[index]
} else {
// 小于次高点时为结果加上次高点和当前数的差值
result += curHeight - height[index]
}
}
return result
}
复杂度分析:
- 时间复杂度:O(N),寻找最大值遍历 N 次,从两边遍历到最高点总共遍历 N 次,所以复杂度是 O(N)。
- 空间复杂度:O(1),仅采用常熟个临时变量。
解法二
动态规划求解,先从左往右遍历找出每个数左边的最大数 maxLeft,再从右往左遍历找出每个数右边的最大数 maxRight,最终遍历每个数,能接的水量是当前数与其 min(maxLeft, maxRight) 的差值。
var trap = function (height) {
let result = 0
// 从左往右遍历找出每个数左边的最大数 maxLeft
const leftMaxArr = findMax(height)
// 从右往左遍历找出每个数右边的最大数 maxRight
const rightMaxArr = findMax([...height].reverse()).reverse()
return height.reduce((total, next, index) => {
const diff = Math.min(leftMaxArr[index], rightMaxArr[index]) - next
return (result += diff > 0 ? diff : 0)
}, 0)
}
var findMax = function (height) {
const res = [height[0]]
for (let i = 1; i < height.length; i++) {
res.push(height[i] > res[i - 1] ? height[i] : res[i - 1])
}
return res
}
复杂度分析:
- 时间复杂度:O(N),来回遍历三次,所以复杂度是 O(N)。
- 空间复杂度:O(N),用了两个长度为 N 的数组缓存每个数的左边最大数和右边最大数,所以复杂度是 O(N)。