← 返回题目列表

盛最多水的容器怎么用双指针解?为什么移动较矮的那条边?

高频 中等 第 6 / 27 题 更新于 2026/07/28
双指针对撞指针贪心

简化版

给一排高度不同的挡板,选两条围成容器,盛水量 = 较矮的高度 × 两板间距。用对撞指针:leftright 从两端开始,每次算面积、更新最大值,然后移动较矮的那条边(左右哪个矮就移哪个)。因为容器高度由短板决定,移动高的一边面积不可能变大,只有移动矮的一边才有希望变大。O(n) 一趟解决。

详细版

int maxArea(int[] h) {
    int left = 0, right = h.length - 1, max = 0;
    while (left < right) {
        int area = Math.min(h[left], h[right]) * (right - left); // 短板 × 宽度
        max = Math.max(max, area);
        if (h[left] < h[right]) left++;    // 移动较矮的一边(左边矮)
        else right--;                       // 右边矮(或相等)就移右
    }
    return max;
}
  • 面积 = min(h[left], h[right]) × (right - left):宽是间距,高是短板。
  • 移动较矮的一边:h[left] < h[right]left++,否则 right--
  • 时间 O(n)、空间 O(1)

完整版教学

一、面积由短板和宽度决定

容器能盛多少水,取决于两条边:

  • 宽度 = 两条边的间距 right - left
  • 高度 = 较矮的那条边(水会从矮的那边溢出,所以受短板限制)。

所以面积 = min(h[left], h[right]) × (right - left)。这个「短板效应」是理解本题的基础——高度永远由矮的一边决定,这也是「移动矮边」策略的依据。

二、为什么移动较矮的一边(核心)

这是本题最关键、最容易想不通的地方。从两端开始,当前面积由短板和宽度决定。下一步移动指针,宽度必然减 1(指针向内收),要想面积变大,只能寄希望于高度变大。分析移动哪一边:

  • 移动较高的那条边:高度仍受较矮的那条边限制(没变),而宽度减小了 → 面积一定变小或不变,毫无意义。
  • 移动较矮的那条边:矮边被换掉,新的边可能更高,高度有机会增大,配合宽度减小,面积有可能变大

所以每次都移动较矮的边,才有可能找到更大的面积;移动高的边是纯亏。这是一种贪心:放弃「不可能更优」的方向。

三、为什么这样不会错过最优解(正确性)

需要论证:移动矮边时,「被丢弃的那些组合」里没有更优解。假设当前 h[left] < h[right],我们要 left++,相当于放弃了「leftright 左边所有位置」的组合。为什么它们不可能更优?

  • 这些组合的宽度都比当前小(right 左移了)。
  • 它们的高度都受 h[left] 限制(因为 h[left] 是短板,和任何 ≤ right 的位置组合,高度都 ≤ h[left])。
  • 宽更小、高不超过 h[left] → 面积都 ≤ 当前面积

所以放弃它们是安全的,不会错过更优解。移动矮边「排除了一整批不可能更优的组合」,这正是双指针 O(n) 且正确的保证。

四、走一个例子

h = [1,8,6,2,5,4,8,3,7]
left=0(1),right=8(7): 面积=min(1,7)*8=8,移矮边(左)left++
left=1(8),right=8(7): 面积=min(8,7)*7=49,移矮边(右)right--
left=1(8),right=7(3): 面积=min(8,3)*6=18,移右 right--
... 继续,最大面积 49

五、和「接雨水」的区别

盛水容器和「接雨水」(LeetCode 42)容易混:

  • 盛最多水的容器:选两条边围成一个容器,求最大面积,双指针移矮边,O(n)。
  • 接雨水:一排柱子,求所有凹槽能接的雨水总量。也能用双指针,但逻辑不同——每个位置的水由「左边最高」和「右边最高」的较小值决定。

两题都用双指针但模型不同,别套错。

六、复杂度与要点

  • 时间 O(n)空间 O(1)
  • 要点:面积 = 短板 × 宽度;每次移动较矮的边(移高边面积必不增);正确性在于「移矮边排除的组合都不可能更优」。

证明移动短板时,可以固定当前短边:任何更靠内的另一端都会让宽度变小,而高度上限仍受这条短边限制,所以面积不可能更大。正是这个支配关系允许一次排除固定短边对应的所有内部组合。

七、把不变量、推演与工程边界落到代码上

算法正确性的核心不是记住某个 while,而是始终维护这个不变量:当前面积由宽度与较短边决定,移动较高边不可能在宽度变小时改善短板。

对应的状态推进是:计算面积后只移动较短的一端;相等时移动任意一端都不会漏掉更优解。每次推进前都应能解释“为什么被排除的部分不可能再包含答案”,否则只是碰巧通过样例。

初始化边界与状态
while 尚未结束:
    根据当前状态作出唯一可证明安全的选择
    更新边界、计数或局部结构
    断言不变量仍然成立
返回不变量在终止状态下推出的答案

复杂度不能只背一个符号。两个指针各最多移动 n-1 次,时间 O(n)。

带数字走一遍:高度 [1,8,6,2,5,4,8,3,7] 的最优是下标 1 与 8,面积 7×7=49。手算时要同时记录下标、区间含义和本轮排除的候选,才能暴露等号与边界错误。

核对项结论
前提仅适用于由两条边围成容器,不等同于接雨水的逐位置累加
时间复杂度O(n)
额外空间O(1)
关键边界面积可能超 int;先计算再移动,不能错误移动较高边
替代方案接雨水需要左右最大值或单调栈

易错点:模板只有在前提和不变量一致时才成立;换了区间定义、重复值规则或输出语义,等号与推进方式就必须重新推导。

实现完成后至少检查五类用例:

  • 空输入或题目允许的最小规模,验证初始化不会越界。
  • 单元素与两个元素,验证循环条件和最后一次推进。
  • 大量重复值,验证相等分支、稳定性或去重语义。
  • 已满足目标性质与极端逆序/偏斜分布,观察最好和退化路径。
  • 上面的数字样例逐轮手算,并与程序记录的状态轨迹逐项对照。

八、常见误区与追问

  • 误区:只要记住模板就适用于所有输入。 本题成立的前提是“仅适用于由两条边围成容器,不等同于接雨水的逐位置累加”,前提被破坏后必须换算法或重新证明。
  • 误区:复杂度只写 O(n) 就算完整。 还要说明输入参数、预处理、递归栈以及最坏退化条件;本题的推导是“两个指针各最多移动 n-1 次,时间 O(n)”。
  • 误区:重复值和边界值不会改变代码。 面积可能超 int;先计算再移动,不能错误移动较高边。
  • 追问:为什么每次推进不会漏掉答案? 因为始终维护“当前面积由宽度与较短边决定,移动较高边不可能在宽度变小时改善短板”,被舍弃区域已由顺序或状态关系证明不可能更优。
  • 追问:用一个数字例子怎么讲? 可以从“高度 [1,8,6,2,5,4,8,3,7] 的最优是下标 1 与 8,面积 7×7=49”开始,逐轮写出状态与被排除区间。
  • 追问:什么时候不该使用这个方案? 当前提不满足或退化代价不可接受时,应考虑“接雨水需要左右最大值或单调栈”。
  • 追问:如何设计自测避免只过样例? 同时覆盖最小规模、重复值、极端分布、无解/边界解,并对关键状态加断言。

九、加强记忆

盛最多水的容器:面积 = 较矮高度 × 宽度(短板效应)。对撞指针从两端开始,每次算面积、移动较矮的那条边。为什么移矮边:宽度每步必减,移高边高度不变(仍受矮边限)→ 面积必减;移矮边高度才可能增大 → 面积才可能变大。正确性:移矮边放弃的组合「宽更小、高不超过矮边」,都不可能更优。O(n) 时间、O(1) 空间。别和「接雨水」混。