接雨水

117 阅读2分钟

相向双指针

左右两个指针,根据当前状态移动指针

盛最多水的容器

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

输入: [1,8,6,2,5,4,8,3,7]
输出: 49 
解释: 图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49

解法分析

左右指针,假设右边值小于左边,此时需要右指针左移

  • 左指针右移(无论如何都会获得更少的雨水)
    • 左指针的值变小了,此时雨水小于指针移动前的雨水
    • 左指针变大了,此时雨水小于移动前的雨水
  • 右指针左移
    • 右指针的值变小了,此时雨水小于指针移动前的雨水
    • 右指针变大了,此时雨水有可能大于移动前的雨水

同理,右边值大于左边时,此时需要左指针右移

左右指针相等时,左右指针随便选择一侧移动即可

时间复杂度:O(n)O(n)

空间复杂度:O(1)O(1)

function maxArea(height: number[]): number {
  let res: number = 0
  let left = 0, right = height.length - 1
  while (left < right) {
    res = Math.max(res, Math.min(height[left], height[right]) * (right - left))
    if (height[left] > height[right]) {
      right--
    } else {
      left++
    }
  }
  return res
};

接雨水

给定 n 个非负整数表示每个宽度为 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 个单位的雨水(蓝色部分表示雨水)。 

解法分析

对于每个位置,能够接多少雨水取决于:

max(0,当前柱子高度min(左边柱子最大高度,右边柱子最大高度))max(0, 当前柱子高度 - min(左边柱子最大高度, 右边柱子最大高度))

而计算左/右边柱子最大高度,则可以使用计算前缀/后缀的方法进行计算

时间复杂度:O(n)O(n)

空间复杂度:O(n)O(n)

function getPreMax(nums: number[]): number[] {
  let pre_max: number[] = [],
    max = -Infinity
  for (let i = 0; i < nums.length; i++) {
    max = Math.max(max, nums[i])
    pre_max.push(max)
  }
  return pre_max
}

function getSufMax(nums: number[]): number[] {
  let suf_max: number[] = [],
    max = -Infinity
  for (let i = nums.length - 1; i >= 0; i--) {
    max = Math.max(max, nums[i])
    suf_max.unshift(max)
  }
  return suf_max
}

function trap(height: number[]): number {
  let res: number = 0,
    pre_max: number[] = getPreMax(height),
    suf_max: number[] = getSufMax(height)
  for (let i = 0; i < height.length; i++) {
    res += Math.min(pre_max[i], suf_max[i]) - height[i]
  }
  return res
}

上面的解法有一个问题,在计算前缀时对数组进行了两次遍历,有没有可能一次遍历完成呢?其实是可以的

假设左右两个指针left, right

  • left > right时,此时right位置上的前缀最大值肯定大于left值,而left又大于right,所以right位置上最多能接多少雨水是可以计算出来的
  • left < right,同理
function trap(height: number[]): number {
  let res: number = 0, pre_max = -Infinity, suf_max = -Infinity
  let left = 0, right = height.length - 1
  while (left <= right) {
    if (height[left] > height[right]) {
      suf_max = Math.max(suf_max, height[right])
      res += suf_max - height[right]
      right--
    } else {
      pre_max = Math.max(pre_max, height[left])
      res += pre_max - height[left]
      left++
    }
  }
  return res
}