图解题库 / 单调栈

42. 接雨水

困难 含交互动画

学完这道你应该能: 说清它为什么用「单调栈」,而不是死记步骤; 合上页面,自己默写出核心代码; 用 30 秒把思路讲清楚。

题目描述

给一排柱子的高度,下雨后能接多少格水?

height = [3, 0, 2, 0, 4]
输出 = 7

思路解析

看清地形:高度是 3、0、2、0、4。中间凹下去的地方能存水,关键是要有「左墙」和「右墙」把水夹住。

栈里存柱子的下标,并且让对应高度从栈底到栈顶越来越矮。一旦遇到一根更高的柱子,就说明「右墙」来了,可以接水了。

i = 0 · 高 3:栈是空的,直接把下标 0 入栈。栈:[0]。

i = 1 · 高 0:高度 0 比栈顶(下标0,高3)矮,符合「递减」,入栈。栈:[0, 1]。

i = 2 · 高 2:高度 2 比栈顶(下标1,高0)!这说明下标 1 这个坑的右墙出现了,可以接水。

i = 2 · 算这格水:弹出坑底下标 1。它的左墙是新栈顶(下标0,高3),右墙是当前(下标2,高2)。能接的水 = (min(3,2) − 0) × 宽1 = 2 格。

i = 2 · 入栈:现在高度 2 比新栈顶(高3)矮了,把下标 2 入栈。栈:[0, 2]。

i = 3 · 高 0:高度 0 比栈顶矮,入栈。栈:[0, 2, 3]。

i = 4 · 高 4:高度 4 比栈顶(下标3,高0)高,右墙来了。注意:4 很高,可能要连续接好几层,咱一层一层来。

i = 4 · 第①层 · 弹坑底:弹出坑底下标 3。它的左墙是下标2(高2)、右墙是下标4(高4)。接水 = (min(2,4)−0) × 宽1 = 2。累计 4。

i = 4 · 还更高吗?:弹完后看新栈顶是下标2(高2)。当前 4 还是比它,说明还能再接一层,继续弹。

i = 4 · 第②层 · 弹坑底:弹出坑底下标 2。左墙下标0(高3)、右墙下标4(高4),宽 3、高 min(3,4)−2=1,接水 3。累计 7

i = 4 · 还更高吗?:新栈顶是下标0(高3)。当前 4 还比它高,再弹一次。

i = 4 · 弹到栈空:弹出下标0后栈空了——没有左墙,水就接不住了,本轮停止。

i = 4 · 入栈:最后把当前下标 4 自己入栈。它成了新的「左墙候选」。

结束:所有柱子处理完,总共接住 7 格水。蓝色就是积水。

单调栈的套路:每来一个新元素,就把栈里「比它矮/小」的旧元素结算掉。每日温度、下一个更大元素都是同款。

参考代码(Python)

Python
1def trap(self, height):
2    stack = list()                 # 存下标,高度递减
3    result = 0
4    for i, h in enumerate(height):
5        while stack and h > height[stack[-1]]:  # 右墙更高 → 接水
6            bottom = stack.pop()   # 坑底
7            if not stack: break    # 没有左墙了
8            left = stack[-1]       # 左墙
9            w = i - left - 1       # 宽
10            h2 = min(height[left], h) - height[bottom]  # 高
11            result += w * h2
12        stack.append(i)
13    return result

复杂度分析

  • 时间复杂度:O(n) —— 每个下标进出栈各一次
  • 空间复杂度:O(n) —— 栈最多存 n 个下标

套路模板

维持栈内递减;每来一个更大的,就把栈里比它小的一一弹出结算。

Python
1# 遇到更大就结算
2stack = []
3for i in range(n):
4    while stack and a[i] > a[stack[-1]]:
5        top = stack.pop()
6        if not stack: break
7        宽 = i - stack[-1] - 1
8        高 = min(a[stack[-1]], a[i]) - a[top]
9    stack.append(i)

易错点

  • 弹出后不判栈空就取 stack[-1] → ✅ if not stack: break(没有左墙就没法接水)
  • 宽度写成 i - top → ✅ 宽 = i - stack[-1] - 1(宽是左右墙之间的距离,不含墙本身)
  • 高度忘了减坑底 → ✅ 高 = min(a[左], a[i]) - a[top](要扣掉坑底已有的高度)

以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握

下一题 →15. 三数之和 ← 返回题库