xxleyi / loop_invariants

记录以及整理「循环不变式」视角下的算法题解

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

leetcode 42. 接雨水

xxleyi opened this issue · comments

题:

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

image

上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 感谢 Marcos 贡献此图。

示例:

输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/trapping-rain-water
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。


解:

/**
 * @param {number[]} height
 * @return {number}
 */


// 第 i 个元素能承接的雨水为 leftMax([0, i]) rightMax([i, len)) 当中的小值减去 height[i]
// sum(map(min(leftMax, rightMax) - cur, height))
// 三次遍历,每次遍历的循环不变式各自维护一个变量,较为简单直接
var trap = function(height) {
  if (height.length < 3) {
    return 0
  }
  const leftMax = Array(height.length).fill(height[0])
  const rightMax = Array(height.length).fill(height[height.length - 1])
  for (let i = 1; i < height.length; i++) {
    leftMax[i] = Math.max(leftMax[i- 1], height[i])
  }
  for (let i = height.length - 2; i >= 0; i--) {
    rightMax[i] = Math.max(rightMax[i + 1], height[i])
  }

  let rain = 0
  for (let i = 0; i < height.length; i++) {
    rain += Math.min(leftMax[i], rightMax[i]) - height[i]
  }
  return rain
};


// sum(innerRectangle(width * height))
// 将雨水计算切成一个个横条,在维护单调递减栈的过程中计算雨水
// 双层循环,但复杂度为线性,循环不变式的维护具象化为单调栈的维护,不熟悉单调栈的话,不容易想到
var trap = function(height) {
  if (height.length < 3) {
    return 0
  }
  const stack = []
  stack.top = function() {
    return this[this.length - 1]
  }

  let rain = 0
  for (let i = 0; i < height.length; i++) {
    while(stack.length > 0 && height[i] > height[stack.top()]) {
      const bottom = stack.pop()
      if (stack.length === 0) {
        break
      }
      const leftTop = stack.top()
      const width = i - leftTop - 1
      const h = Math.min(height[i], height[leftTop]) - height[bottom]
      rain += h * width
    }
    stack.push(i)
  }
  return rain
}

// sum(map(min(leftMax, rightMax) - cur, height))
// 仔细观察一下,发现左右同时遍历,可边更新,边计算
// 方法一可进一步优化为左右双指针,节省一次遍历,以及两个数组
// 这样的话,一次遍历,循环不变式需要同时维护五个变量,不容易想到
var trap = function(height) {
  if (height.length < 3) {
    return 0
  }
  let left = 1
  let right = height.length - 2
  let rain = 0
  let leftMax = height[0]
  let rightMax = height[height.length - 1]
  while (left <= right) {
    leftMax = Math.max(leftMax, height[left])
    rightMax = Math.max(rightMax, height[right])
    if (leftMax < rightMax) {
      rain += leftMax - height[left]
      left++
    } else {
      rain += rightMax - height[right]
      right--
    }
  }
  return rain
}