直接遍历无法算出正确雨水量,因为每个位置存水量取决于左右两侧最高墙而非相邻元素;需预处理leftmax和rightmax数组,再按min(leftmax[i],rightmax[i])−height[i]累加有效差值。

为什么直接遍历数组算不出正确雨水量
因为每个位置能存多少水,取决于它左右两侧的最高墙——不是左边所有墙里最高的,就是右边所有墙里最高的。只看相邻元素或者单次从左到右扫,会漏掉远端的“围挡”作用。比如 [0,1,0,2,1,0,1,3,2,1,2,1] 中索引 2 的位置,左边最高是 1,右边最高是 3,所以能存 min(1,3) - 0 = 1 单位水。没预处理左右最大值,这一步就无从下手。
用两个数组预处理 leftMax 和 rightMax 最稳妥
这是最直观、不易出错的解法,时间换空间,逻辑清晰,适合调试和理解原理。核心就是为每个下标 i 算出:leftMax[i] 表示 height[0..i] 的最大值,rightMax[i] 表示 height[i..n-1] 的最大值。
实操建议:
- 初始化
leftMax[0] = height[0],然后从左到右循环:leftMax[i] = max(leftMax[i-1], height[i]) - 初始化
rightMax[n-1] = height[n-1],然后从右到左循环:rightMax[i] = max(rightMax[i+1], height[i]) - 最后遍历一次:对每个
i,若min(leftMax[i], rightMax[i]) > height[i],则累加差值
注意:边界位置(首尾)一定存不了水,因为缺一侧墙,但用上述逻辑自动处理为 0,无需特判。
双指针法节省空间但容易写错边界条件
当内存受限或想优化空间复杂度到 O(1) 时用。本质是模拟两个指针从两端向中间走,维护当前已知的左/右最大值,利用“较矮那边决定当前能存多少水”这一性质推进。
关键判断逻辑:
- 始终移动
left或right中指向更矮柱子的那一侧 - 如果
height[left] ,说明 <code>left处的瓶颈在左边(因为右边还有更高的墙兜底),此时能存水量为leftMax - height[left],然后更新leftMax并右移left - 反之处理
right侧 - 停止条件是
left >= right,不是left == right—— 否则中间那个位置可能被跳过
常见错误:把比较条件写成 leftMax ,其实应该用当前柱高,否则逻辑断裂;还有人忘了在移动前先计算当前格子的蓄水量。
单调栈适合找“凹槽”,但要小心栈中存的是下标
这是最容易卡壳的写法:栈里必须存 int 下标,不是高度值。因为我们需要知道宽度(下标差)来算面积。每次遇到比栈顶高的新柱子,就弹出栈顶,把它当作“凹槽底”,用新柱子和新的栈顶(左墙)夹出一个可积水区域。
实操要点:
- 栈初始为空,遍历
i从0到n-1 - 当
!stack.empty() && height[i] > height[stack.top()]时进入 while 循环 - 弹出
top = stack.top(); stack.pop();,此时若栈非空,left = stack.top()是左边界,i是右边界,宽度为i - left - 1,高度为min(height[left], height[i]) - height[top] - 只在高度差 > 0 时才累加,避免负数
最容易忽略的一点:同一个凹槽可能被多次拆分计算(比如宽凹槽被多个中等高度柱子切开),单调栈天然支持这种分段统计,但初学者常误以为要一次性算整个洼地。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











