「前端刷题」42. 接雨水

172 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

题目

给定 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

思路

方法一:暴力解法

直观想法:

直接按照题目的描述进行,对于数组中的每一个元素,我们找出下雨后水能达到的最高位置,等于两边最大高度较小值减去当前高度的值。

/**
 * @param {number[]} height
 * @return {number}
 */
// 按照暴力解法 如果i是2 左边的最大值是索引为1的位置,这个位置值是1
// 右边最大值是3,当前这个位置能够存储多少水,是由短的一边决定的
// 对于i为3的位置来说,算上当前的索引位置的高度左边的最大值是2
// 右边的最大值是还是3,此时左右两边的最小值是2 当前索引处的高度
// 也是2 这样一来 ans 就是0。说明索引为3的位置不能存储雨水
var trap = function (height) {
  let res = 0;
  for (let i = 0; i < height.length; i++) {
    let max_letf = 0;
    let max_right = 0;
    // 以当前curr 这个位置为基准,找出左边的边界的最大值 找出右边边界的最大值
    for (let j = i; j >= 0; j--) {
      max_letf = Math.max(max_letf, height[j])
    }
    for (let j = i; j < height.length; j++) {
      max_right = Math.max(max_right, height[j])
    }

    res += Math.min(max_letf, max_right) - height[i]
  }
  return res
};

方法二: 双指针解法

我们先明确几个变量的含义:

left_max: 左边的最大值,它是从左往右遍历找到的
right_max: 右边的最大值,它是从右往左遍历找到的
left: 从左往右处理的当前下标
right: 从右往左处理的当前下标

在某个位置i处,它能存的水,取决于它左右两边的最大值中较小的一个,这点很好理解,和木桶原理类似,取决于短板。这个思路和暴力解法中使用左边最大值还是右边最大值减去当前这个位置的高度的思路是类似的。

当我们从左往右处理left下标时候,左边的最大值left_max对它而言是可信的,但是right_max 对它而言是不可信的, 因为中间没有遍历到的部分是否有更大的是未知的,对于left下标而言,right_max未必就是它右边最大的值。

当我们从右向左处理right下标时候,右边的最大值right_max对它而言是可信的,但是left_max对它而言是不可信的。

对于位置left而言,它左边的最大值一定是left_max,右边的最大值“大于等于right_max”, 此时, 如果left_max < right_max 成立,还记的我们上文说的存储水的基本条件是:取决于它左右两边的最大值中较小的一个,那么它就知道自己能存多少水了,无论右边将来会不会出现更大的right_max,都不影响这个结果,所以当 left_max < right_max 时,我们就希望去处理left下标,反之,我们希望去处理right下标。

代码:

/**
 * @param {number[]} height
 * @return {number}
 */
var trap = function (height) {
  let left = 0;
  let right = height.length - 1;
  let res = 0;
  let leftMax = 0;
  let rightMax = 0;
  while (left < right) {
    if (height[left] < height[right]) {
      leftMax = Math.max(height[left], leftMax);
      res += leftMax - height[left];
      left++;
    } else {
      rightMax = Math.max(height[right], rightMax);
      res += rightMax - height[right];
      right--;
    }
  }
  return res;
};

复杂性分析

  • 时间复杂度:O(n)。单次遍历的时间O(n)。
  • 空间复杂度:O(1)的额外空间。