
本题要求计算每个位置上方能存多少水。
对于位置 i,它能接到的水量由左右两侧最高柱子的较小值决定:
如果提前维护左右两侧的最高值,就可以避免对每个位置都向两边扫描。
这题可以用两种常见方法:
leftMax 和 rightMax,每次处理较矮的一侧。双指针解法使用闭区间 [left, right]:
left:从数组左侧向右移动。right:从数组右侧向左移动。leftMax:left 左侧以及当前位置见过的最高柱子。rightMax:right 右侧以及当前位置见过的最高柱子。ans:累计接到的雨水总量。关键判断:
height[left] < height[right],说明 left 位置右侧至少存在一个比它高的柱子,此时 left 能接多少水只取决于 leftMax。right,它能接多少水只取决于 rightMax。单调栈解法中:
height[i] 如果高于栈顶柱子,说明栈顶可能是一个低洼位置,可以开始结算。每轮比较 height[left] 和 height[right]:
height[left] < height[right]:
leftMax = Math.max(leftMax, height[left])。left 能接的水是 leftMax - height[left]。left++。rightMax = Math.max(rightMax, height[right])。right 能接的水是 rightMax - height[right]。right--。为什么可以这样处理?
当 height[left] < height[right] 时,右侧已经有一个高度至少为 height[right] 的边界。对 left 来说,短板只可能是左侧最高柱子 leftMax。如果 leftMax 比当前位置高,就能接水;否则接不到水。
从左到右遍历每根柱子 i:
height[i] <= height[stack[stack.length - 1]],直接把 i 入栈。height[i] > height[stack[stack.length - 1]],说明出现了右边界:
bottom,它是低洼位置。leftBoundary。i - leftBoundary - 1。Math.min(height[leftBoundary], height[i]) - height[bottom]。width * boundedHeight。双指针解法中,每次处理 left 或 right 时,就可以立刻更新答案:
或:
单调栈解法中,只有当当前柱子比栈顶柱子高,并且弹出低洼位置后仍然存在左边界时,才更新答案。
边界条件:
height.length < 3,不足以形成左右边界,直接返回 0。0,不需要特殊处理。易错点:
height[left] 和 height[right] 的大小,不是 leftMax 和 rightMax 的大小。leftMax 和 rightMax 要先更新,再计算当前位置能接的水,保证结果不会为负数。Math.min(leftHeight, rightHeight) - bottomHeight。使用示例:
下面分别展示两种方案的图解。图片负责呈现过程,表格负责对应代码里的关键状态。

双指针每一步只结算较矮的一侧。
| 步骤 | 处理位置 | leftMax | rightMax | 本轮接水 | 累计答案 |
|---|---|---|---|---|---|
| 初始 | - | 0 | 0 | - | 0 |
| 1 | left = 0,高度 4 | 4 | 0 | 4 - 4 = 0 | 0 |
| 2 | left = 1,高度 2 | 4 | 0 | 4 - 2 = 2 | 2 |
| 3 | left = 2,高度 0 | 4 | 0 | 4 - 0 = 4 | 6 |
| 4 | left = 3,高度 3 | 4 | 0 | 4 - 3 = 1 | 7 |
| 5 | left = 4,高度 2 | 4 | 0 | 4 - 2 = 2 | 9 |
最终每个位置的接水量是:
所以总水量为:

单调栈保存下标,普通入栈不产生水量。只有当前柱子比栈顶高、触发弹栈时,才需要结算。
入栈过程:
| 遍历位置 | 动作后栈状态 | 说明 |
|---|---|---|
i = 0 | [0] | 栈空,直接入栈 |
i = 1 | [0, 1] | 2 <= 4,保持单调递减 |
i = 2 | [0, 1, 2] | 0 <= 2,保持单调递减 |
i = 3 | [0, 3] | 3 触发弹出 2、1 后入栈 |
i = 4 | [0, 3, 4] | 2 <= 3,保持单调递减 |
i = 5 | [5] | 5 触发弹出 4、3、0 后入栈 |
有效结算过程:
| 当前右边界 | 弹出的低洼位置 | 左边界 | 宽度 | 有效高度 | 本轮接水 | 累计答案 |
|---|---|---|---|---|---|---|
i = 3,高度 3 | bottom = 2,高度 0 | 1,高度 2 | 1 | min(2, 3) - 0 = 2 | 2 | 2 |
i = 3,高度 3 | bottom = 1,高度 2 | 0,高度 4 | 2 | min(4, 3) - 2 = 1 | 2 | 4 |
i = 5,高度 5 | bottom = 4,高度 2 | 3,高度 3 | 1 | min(3, 5) - 2 = 1 | 1 | 5 |
i = 5,高度 5 | bottom = 3,高度 3 | 0,高度 4 | 4 | min(4, 5) - 3 = 1 | 4 | 9 |
弹出 0 后栈为空,说明没有左边界,所以不再产生水量。最终总水量也是 9。
双指针解法:
O(n),left 和 right 都只会向中间移动,每个位置最多处理一次。O(1),只使用了常数个变量。单调栈解法:
O(n),每个下标最多入栈一次、出栈一次。O(n),最坏情况下栈中会保存所有柱子的下标。