接雨水 图解题解
柱子围出的坑能装多少水?单调栈让每根柱子只进出一次,左墙右墙一找到就立刻结算。
栈里存柱子的下标,始终让对应高度从栈底到栈顶单调递减。扫到一根更高的柱子时,把栈顶弹出——它就是「坑底」;弹出后新的栈顶是左墙,当前这根更高的柱子是右墙;水位 = min(左墙高, 右墙高) 减去坑底高,再乘以左右墙的距离。若栈里还有更矮的元素,继续弹、继续算更宽的一层,直到栈为空或栈顶不比当前矮为止。
这道题到底在问什么
- 输入
- height=[0,1,0,2,1,0,1,3,2,1,2,1]
- 输出
- 6
最优解:为什么这么做
一句话答案:LeetCode 42 接雨水的最优解是对撞双指针:左右各一个指针往中间走,同时维护左边最高墙 leftMax 和右边最高墙 rightMax,每轮只结算较矮的那一端——该格接水量等于对应一侧的最高墙减自身高度。每格只被访问一次,时间 O(n)、空间 O(1),优于按列扫描的 O(n²) 和预处理数组的 O(n) 空间做法。
接雨水这道题在问什么
给一排宽度为 1、高度不等的柱子,下雨后凹槽里会存水,求总共能接多少格水。关键的物理直觉是按「竖列」看:某一列上方能存的水,取决于它左边的最高柱和右边的最高柱——水面高度是两者中较矮的那个,再减去这一列自身的高度,就是这一列的储水量;如果自身比两边最高墙还高,这一列存不了水。
从每列扫两遍到双指针的推导
把上面的直觉直接翻译成代码,就是对每一列分别向左、向右扫一遍找最高墙,n 列各扫 O(n),总共 O(n²),柱子一多就太慢。第一步优化是用两个数组预处理出「每个位置左侧最高」和「右侧最高」,查询变 O(1),时间降到 O(n),但要 O(n) 的额外空间。
再进一步观察:结算一列时,其实不需要同时精确知道左右两边的最高墙,只需要知道「较矮的那一边有多高」。这就是双指针版本省掉数组的突破口。
为什么只看较矮的一端就能定水量
让左指针 l 从最左、右指针 r 从最右往中间走,一路维护 leftMax(l 左侧走过的最高柱)和 rightMax(r 右侧走过的最高柱)。每轮比较两端柱高:假设左端更矮,那么右边一定存在一根不低于当前右端的柱子挡着,水面下限已经被左侧锁死——这一格的水量就是 leftMax 减去自身高度,跟右边究竟多高无关。右端更矮时对称处理。
这就是「木桶短板」的贪心论证:较矮的一端是当前的瓶颈,它的储水量当场可以拍板,所以放心结算并把该指针往中间挪一格。每格恰好被结算一次,两个指针合计走完整个数组,正确性和 O(n) 的效率同时成立。
循环里最容易写错的三个地方
一是减错基准:某格的水量是「一侧最高墙减自身」,不是拿相邻柱子或自身高度做差;二是指针移动方向:每轮必须移较矮的那一端,若总是移左指针,右侧瓶颈没被结算就被跳过,答案偏小;三是判断顺序:当前柱不低于本侧 max 时它自己就是新墙,要先更新 max 且这一格不接水,只有严格矮于 max 才累加 max 减自身。
复杂度与另一条单调栈思路
双指针版时间 O(n):l 和 r 相向而行,每格只处理一次;空间 O(1):全程只有 l、r、leftMax、rightMax 和累加器五个变量。
这道题还有一条按「横条」结算的思路——单调递减栈:遇到比栈顶高的柱子就出栈结算一层横向的水。同样是 O(n) 时间,但要 O(n) 的栈空间。面试里双指针是空间最优解,单调栈则更适合作为理解「按层算水」的补充视角,两种都值得会讲。
▶ 动画逐步走查(共 25 步)——想跟着动画一帧帧对照就展开
- 3双指针的妙处:从两端往中间走,只盯当前较矮的一侧,就能现场定出这格的水,不必每格都向两边扫一遍。
- 4初始:l 指向最左(下标 0),r 指向最右(下标 11)。leftMax/rightMax 先记 0。 谁矮谁先决定接水。
- 5左端较矮(高 0 < 右端 1)。它比左墙 leftMax 还高 → 它就是新左墙 leftMax=0,自身接不到水。
- 6左侧处理完,l 右移到下标 1。继续比较两端。
- 7右端较矮或持平(高 1 ≤ 左端 1)。它比右墙 rightMax 还高 → 它就是新右墙 rightMax=1,自身接不到水。
- 8右侧处理完,r 左移到下标 10。继续比较两端。
- 9左端较矮(高 1 < 右端 2)。它比左墙 leftMax 还高 → 它就是新左墙 leftMax=1,自身接不到水。
- 10左侧处理完,l 右移到下标 2。继续比较两端。
- 11左端较矮(高 0 < 右端 2)。左墙 leftMax=1 比它高 → 这格接水 1-0=1。累计 1。
- 12左侧处理完,l 右移到下标 3。继续比较两端。
- 13右端较矮或持平(高 2 ≤ 左端 2)。它比右墙 rightMax 还高 → 它就是新右墙 rightMax=2,自身接不到水。
- 14右侧处理完,r 左移到下标 9。继续比较两端。
- 15右端较矮或持平(高 1 ≤ 左端 2)。右墙 rightMax=2 比它高 → 这格接水 2-1=1。累计 2。
- 16右侧处理完,r 左移到下标 8。继续比较两端。
- 17右端较矮或持平(高 2 ≤ 左端 2)。它比右墙 rightMax 还高 → 它就是新右墙 rightMax=2,自身接不到水。
- 18右侧处理完,r 左移到下标 7。继续比较两端。
- 19左端较矮(高 2 < 右端 3)。它比左墙 leftMax 还高 → 它就是新左墙 leftMax=2,自身接不到水。
- 20左侧处理完,l 右移到下标 4。继续比较两端。
- 21左端较矮(高 1 < 右端 3)。左墙 leftMax=2 比它高 → 这格接水 2-1=1。累计 3。
- 22左侧处理完,l 右移到下标 5。继续比较两端。
- 23左端较矮(高 0 < 右端 3)。左墙 leftMax=2 比它高 → 这格接水 2-0=2。累计 5。
- 24左侧处理完,l 右移到下标 6。继续比较两端。
- 25左端较矮(高 1 < 右端 3)。左墙 leftMax=2 比它高 → 这格接水 2-1=1。累计 6。
- 26左侧处理完,l 右移到下标 7。继续比较两端。
- 27两指针相遇,全部处理完。所有蓝色水柱加起来 = 6,就是这排柱子能接住的总水量。
⚠️ 容易写错的地方
✗ 错:用自己高度直接减
✓ 对:减的是 min(左最高,右最高)
水被两边较矮的墙挡住,不是被自己挡
✗ 错:总移动左指针
✓ 对:每轮只移“较矮”那一端
矮端才是当前能定水的瓶颈,移矮端才安全
✗ 错:先更新 max 再判断
✓ 对:矮端 < 当前 max 才接水
否则它本身就是新墙,自己接不到水
完整代码(Python / C++ / Java)
Python
def trap(height):
l, r = 0, len(height) - 1
lmax = rmax = total = 0
while l < r:
if height[l] < height[r]:
if height[l] >= lmax: lmax = height[l]
else: total += lmax - height[l]
l += 1
else:
if height[r] >= rmax: rmax = height[r]
else: total += rmax - height[r]
r -= 1
return totalC++
int trap(vector<int>& h){
int l = 0, r = h.size() - 1;
int lmax = 0, rmax = 0, total = 0;
while(l < r){
if(h[l] < h[r]){
h[l] >= lmax ? lmax = h[l] : total += lmax - h[l];
l++;
} else {
h[r] >= rmax ? rmax = h[r] : total += rmax - h[r];
r--;
}
}
return total;
}Java
public int trap(int[] height) {
int l = 0, r = height.length - 1;
int lmax = 0, rmax = 0, total = 0;
while (l < r) {
if (height[l] < height[r]) {
if (height[l] >= lmax) lmax = height[l];
else total += lmax - height[l];
l++;
} else {
if (height[r] >= rmax) rmax = height[r];
else total += rmax - height[r];
r--;
}
}
return total;
}复杂度
时间
O(n)
左右指针合计走一遍
空间
O(1)
只用 l/r/leftMax/rightMax 几个变量
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 接雨水 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么每次移动“较矮”的那一端是对的?+
若 height[l] < height[r],则左侧这格的水只受 leftMax 限制——因为右边至少有 height[r] > height[l] 兜底,右墙一定不矮于左墙,可放心用 leftMax 结算并右移 l。
还有哪些解法?+
① 动态规划:预存每格的 leftMax[]、rightMax[] 两个数组,O(n) 时间 O(n) 空间;② 单调栈:逐格入栈、遇到更高柱时一层层结算横向积水。双指针把空间压到 O(1)。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 接雨水 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。