← 双指针 · Two Pointer / 接雨水:不看整体,只看每根柱子 待审核 5 / 7
Trapping Rain Water · 对撞

接雨水:不看整体,只看每根柱子

LeetCode 42:一排宽度为 1 的柱子,求下雨后能接多少水。这题真正的难点是建模。顺着直觉去找「有几个坑、每个坑装多少」,会发现坑的形状千变万化,程序很难写。换个角度——不分析整体,只分析局部:对每一个位置 i,求它头顶能积多高的水。

答案只取决于左右两边的「天花板」:
[i]=min(左边最高柱,右边最高柱)自己水[i] = \min (左边最高柱, 右边最高柱) - 自己(若为负则是 0)。
于是问题变成:怎么求每个位置左右两侧的最大值?下面三种解法层层递进。

1 · 解法三:双指针 — O(1) 空间,边走边算

另外两种解法(见本页下方)都要先把所有位置的左右最大值存下来O(n)O(n) 空间)。双指针的优势在于不必把两边最大值都精确求出。left 从左、right 从右对撞,维护 leftMax / rightMax。关键性质:leftMax < rightMax,那么 left 这一列的水位就由 leftMax 唯一确定——右边是否还有更高的柱子已不影响结果,因为「短板」已经确定在左边。于是结算 left、指针内移;反之结算 right。

为什么 leftMax < rightMax 时不用管右边到底多高?因为 left 位置的水位 = min(左最高,右最高)\min (真\cdot 左最高, 真\cdot 右最高)。此刻 leftMax 已是 left 左侧(含自己)的真实最高;而右侧真实最高 ≥ rightMax > leftMax,所以 min 必定取 leftMax。信息已经够了,根本不必把右边走完。这就是双指针把 O(n)O(n) 空间压成 O(1)O(1) 的关键。

2 · 对照:另外两种解法

解法一 · 动态规划:正着、倒着各扫一遍,把每个位置的左/右最大值预存进数组,再逐位求和。直观,但要 O(n)O(n) 额外空间。

解法二 · 单调栈:要确定一个坑能装水,得知道「后面出现了更高的柱子」——凡是需要回退的场景,栈都是好选择。维护一个高度递减的单调栈,遇到比栈顶高的柱子就弹栈、按「左墙、底、右墙」一层层横向结算积水。

三者殊途同归。DP 用空间换清晰、栈擅长「需要回退」的结构、双指针最省空间。对暴力解法的思考是优化的基础——这题难就难在连暴力枚举都不好想;一旦把视角从「整体的坑」切到「每根柱子头顶的水」,三条路就都通了。双指针的第三个流派——靠速度差工作的快慢指针——则主要服务于链表结构。