在问 · 算法题解

42. 接雨水

题目描述

输入长度为 n 的非负整数数组 height。把它看成一排宽度都为 1 的柱子,第 i 根柱子高 height[i],相邻柱子紧挨在一起。

下雨后,柱子之间的凹槽会积水。某一列上方的水面高度,取决于它左边最高的柱子和右边最高的柱子中较矮的那个。

返回所有列加起来一共能存住多少单位的水。

示例 1
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6

解释:这组柱子之间的几处凹槽合计能存 6 个单位的水。

示例 2
输入:height = [4,2,0,3,2,5]
输出:9

解释:下标 1 到 4 处分别能存 2、4、1、2 个单位,共 9。

数据范围

  • n == height.length
  • 1 <= n <= 2 * 10^4
  • 0 <= height[i] <= 10^5

解题思路

左右指针维护已处理侧的最高墙。若 height[l] <= height[r],先更新 left_max,再累加 left_max-height[l] 并右移 l;否则更新 right_max,累加 right_max-height[r] 并左移 r。

为什么正确

处理较矮一侧时,该侧的最高边界已经确定,当前位置水量可安全加入答案。

复杂度分析

时间 O(n),空间 O(1)。

易错点

只看相邻柱高会漏掉由远处边界形成的水槽。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def trap(self, height):
        l,r=0,len(height)-1; left_max=right_max=0; ans=0
        while l < r:
            if height[l] <= height[r]:
                left_max=max(left_max,height[l]); ans += left_max-height[l]; l += 1
            else:
                right_max=max(right_max,height[r]); ans += right_max-height[r]; r -= 1
        return ans

载入编辑器,自己跑一遍 →

其他解法

前后缀最大值和双指针都按列求水量,后者把 O(n) 数组压成 O(1) 变量;单调栈按层求水量,同样 O(n) 时间但需要栈空间。

解法 2:前后缀最大值 时间 O(n),空间 O(n)

位置 i 的水面高度等于它左侧最高柱和右侧最高柱中的较小者。从左到右算出 leftMax[i],从右到左算出 rightMax[i]。答案是所有 min(leftMax[i], rightMax[i]) - height[i] 之和。

思路最直接,是理解双指针解法的前置步骤。需要两个长度为 n 的数组;双指针正是把这两个数组压缩成两个变量。

class Solution:
    def trap(self, height):
        n = len(height)
        if n == 0:
            return 0
        left_max = [0] * n
        right_max = [0] * n
        left_max[0] = height[0]
        for i in range(1, n):
            left_max[i] = max(left_max[i - 1], height[i])
        right_max[n - 1] = height[n - 1]
        for i in range(n - 2, -1, -1):
            right_max[i] = max(right_max[i + 1], height[i])
        ans = 0
        for i in range(n):
            ans += min(left_max[i], right_max[i]) - height[i]
        return ans

解法 3:单调栈 时间 O(n),空间 O(n)

维护一个下标栈,栈内高度单调不增。遇到比栈顶更高的柱子时,弹出栈顶作为坑底,新栈顶是左墙,当前柱子是右墙。这一层水的体积为 (i - left - 1) × (min(左墙, 右墙) - 坑底),累加到答案。

按层横向计算水量,和前两种按列纵向计算的思路互补。需要 O(n) 栈空间,代码细节比双指针多,但同一套路可用于柱状图最大矩形等题。

class Solution:
    def trap(self, height):
        ans = 0
        stack = []
        for i, h in enumerate(height):
            while stack and h > height[stack[-1]]:
                bottom = stack.pop()
                if not stack:
                    break
                left = stack[-1]
                width = i - left - 1
                bounded = min(height[left], h) - height[bottom]
                ans += width * bounded
            stack.append(i)
        return ans

双指针 专题的其他题目

查看 双指针 全部题目 →