20210402 LeetCode 每日一题(直方图的水量)

137 阅读2分钟

题目描述

原题链接:直方图的水量

给定一个直方图(也称柱状图),假设有人从上面源源不断地倒水,最后直方图能存多少水量?直方图的宽度为 1。

示例

rainwatertrap.png

上面是由数组 [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)。