【力扣-11. 盛最多水的容器】Python笔记

3 阅读8分钟

力扣 11. 盛最多水的容器:双指针为什么只移动短板?

这道题是双指针里的经典题。
很多人能记住代码,但一到面试里就解释不清楚:为什么每次只移动较短的那一边?
这篇文章就从知识点出发,把这道题完整讲透。


一、先别急着做题,先补一个核心知识点

在正式讲题之前,先理解一个非常重要的结论:

一个容器最多能装多少水,不取决于更高的那块板,而取决于更短的那块板。

为什么?

因为水位不可能超过短板。
哪怕另一边再高,只要短板不够高,水就会从短板那一边流出去。

所以,容器的容量公式是:

面积 = 宽度 × 高度

在这道题里:

  • 宽度 = 两根柱子之间的距离
  • 高度 = 两根柱子中较短的那一根

所以可以写成:

area = (right - left) * min(height[left], height[right])

这就是整道题最核心的公式。

二、这道题考察的知识点有哪些?

这道题虽然不难,但很有代表性,主要考察下面几个知识点:

1. 双指针

双指针通常用于:

  • 数组或字符串
  • 从两端向中间逼近
  • 在降低时间复杂度的同时保留有效信息

这道题如果暴力枚举所有两根柱子的组合,复杂度会是 O(n^2)
而双指针能把它优化到 O(n)


2. 贪心思想

每一步我们都要决定:

  • 左指针动,还是右指针动?

这里并不是随便移动,而是有明确策略:

每次移动较短的那一边。

因为较长的那一边不是当前瓶颈,移动它没有意义。


3. 短板效应

这道题本质就是“短板效应”的应用:

  • 容器容量由短板决定
  • 想让容量变大,就必须尝试寻找更高的短板
  • 所以每次只能舍弃当前较短的一边

这个思想非常重要,也是理解双指针正确性的关键。


三、题目描述

给定一个长度为 n 的整数数组 height,其中 height[i] 表示第 i 根柱子的高度。

从数组中选择两根柱子,和 x 轴一起构成一个容器,问这个容器最多能装多少水。

示例

输入:height = [1,8,6,2,5,4,8,3,7]
输出:49

解释:

  • 选择下标 18
  • 高度分别是 87
  • 宽度是 8 - 1 = 7
  • 容器高度取较小值 7

所以面积为:

7 * 7 = 49

四、先从最直观的暴力思路开始

最容易想到的办法就是:

  • 枚举每一对柱子
  • 计算它们组成的容器面积
  • 取最大值

代码逻辑大概是这样:

max_area = 0
for i in range(n):
    for j in range(i + 1, n):
        area = (j - i) * min(height[i], height[j])
        max_area = max(max_area, area)

这种做法当然能做出来,但问题也很明显:

  • 一共有 n 个位置
  • 两两组合要枚举 O(n^2)

所以时间复杂度是:O(n^2)

五、双指针优化:为什么能从 O(n^2) 变成 O(n)?

我们定义两个指针:

  • left = 0
  • right = len(height) - 1

分别指向数组最左边和最右边。

这样一来,初始状态下的宽度最大。
然后我们一边计算当前面积,一边尝试缩小区间。

当前面积怎么计算?

area = (right - left) * min(height[left], height[right])

这个没有问题,关键在于:

计算完当前面积后,下一步该移动谁?


六、为什么只移动较短的一边?

这是整道题最关键的地方。

假设当前:

  • height[left] < height[right]

那么此时容器高度由谁决定?

显然是:

min(height[left], height[right]) = height[left]

也就是说,左边是短板

当前面积是:

(right - left) * height[left]

现在如果你去移动右指针,会发生什么?

  • 宽度一定变小
  • 容器高度仍然最多受左边这个短板限制
  • 即使右边变高,也没用,因为短板还是左边

所以移动右边,不可能让结果更优。

而如果移动左边,就有机会遇到一个更高的柱子:

  • 虽然宽度变小了
  • 但短板可能变高
  • 面积才有可能变大

所以结论就是:

谁短就移动谁。

同理:

  • 如果 height[left] > height[right],就移动 right
  • 如果两边一样高,移动哪边都可以

七、把这个过程彻底讲明白

情况 1:左边更短

如果:

height[left] < height[right]

那么当前面积由左边决定。

这时候:

  • 右边再怎么移动,都无法突破左边这块短板的限制
  • 所以只能尝试移动左边,寻找更高的柱子

于是:

left += 1

情况 2:右边更短

如果:

height[left] > height[right]

那么当前面积由右边决定。

这时候:

  • 左边再高也没用
  • 真正限制容量的是右边
  • 所以应该移动右边,尝试找到更高的柱子

于是:

right -= 1

情况 3:两边一样高

如果:

height[left] == height[right]

说明两边都是短板,移动任意一边都可以。

实际写代码时,通常直接归到 else 里面处理即可。


八、双指针解法完整代码

from typing import List

class Solution:
    def maxArea(self, height: List[int]) -> int:
        left = 0
        right = len(height) - 1
        max_area = 0

        while left < right:
            width = right - left
            h = min(height[left], height[right])
            area = width * h
            max_area = max(max_area, area)

            if height[left] < height[right]:
                left += 1
            else:
                right -= 1

        return max_area

九、代码逐步讲解

1. 初始化左右指针

left = 0
right = len(height) - 1

一开始让容器宽度最大。


2. 记录最大面积

max_area = 0

用于保存遍历过程中的最优答案。


3. 循环条件

while left < right:

只要左右指针没有相遇,就还能形成容器。


4. 计算当前面积

width = right - left
h = min(height[left], height[right])
area = width * h

这里非常关键:

  • 宽度由指针距离决定
  • 高度由较短柱子决定

5. 更新答案

max_area = max(max_area, area)

保留历史最大值。


6. 移动短板

if height[left] < height[right]:
    left += 1
else:
    right -= 1

这是整道题的核心逻辑。

记住一句话:

移动高板没有意义,只有移动短板才可能得到更大的面积。


十、用示例手推一遍过程

数组:

height = [1,8,6,2,5,4,8,3,7]

第 1 次

  • left = 0,高度 1
  • right = 8,高度 7

当前面积:

(8 - 0) * min(1, 7) = 8

左边更短,移动左指针。


第 2 次

  • left = 1,高度 8
  • right = 8,高度 7

当前面积:

(8 - 1) * min(8, 7) = 7 * 7 = 49

更新最大值为 49

右边更短,移动右指针。


后续继续移动

虽然之后还会产生很多组合,但都无法超过 49,最终答案就是:

49

十一、复杂度分析

时间复杂度

O(n)

因为左右指针每次至少移动一个,总共最多移动 n 次。


空间复杂度

O(1)

只使用了常数级额外空间。


十二、这道题最容易犯的错

1. 搞错面积公式

很多人会写成:

(right - left) * max(height[left], height[right])

这是错误的。

因为容器高度不是由高板决定,而是由短板决定。

正确写法一定是:

(right - left) * min(height[left], height[right])

2. 不理解为什么移动短板

这是这道题最常见的面试追问。

你一定要能说清楚:

  • 宽度无论如何都会变小
  • 所以想让面积变大,只能期待高度变大
  • 而当前高度受短板限制
  • 因此只能移动短板,去寻找更高的板

这才是双指针成立的根本原因。


3. 先移动指针,再算面积

有些人会把顺序写反:

  • 先移动指针
  • 再计算面积

这样会漏掉当前区间的答案。

正确顺序是:

  1. 先计算当前面积
  2. 更新最大值
  3. 再移动短板

十三、这道题背后的通用方法

这道题不只是背一个模板,它背后有一类很重要的思想。

1. 当答案由“两端关系”决定时,优先考虑双指针

如果一个问题:

  • 和左右边界有关
  • 可以从两端往中间缩小范围
  • 每次缩小都能排除一部分无效解

那通常都可以想想双指针。


2. 贪心不是乱猜,而是有依据地舍弃

这道题每次移动短板,不是拍脑袋决定的,而是因为:

  • 移动长板无法突破当前短板限制
  • 所以长板对应的一整类情况都可以直接舍弃

这就是典型的贪心剪枝。


3. 先想清楚“什么因素限制了答案”

这道题里,真正限制面积的是:

  • 宽度
  • 短板高度

其中宽度一定会随着指针移动而减小,
所以唯一可能带来提升的,就是让短板更高。

一旦抓住这个限制因素,整个题目就通了。


十四、总结

力扣 11 是一道非常经典的双指针题,核心就一句话:

面积由宽度和短板共同决定,而宽度在缩小,所以只能通过移动短板去寻找更优解。

需要真正记住的不是代码,而是下面这套逻辑:

  1. 容器面积公式是
    area = (right - left) * min(height[left], height[right])
  2. 每次先计算当前面积
  3. 谁短就移动谁
  4. 因为移动长板不会让答案变得更优

所以这道题最本质的结论就是:

双指针的关键,不是两边一起动,而是每次只移动限制答案的那一边。

十五、一句话记忆

盛水看短板,变大靠换短板。


如果你也是刚学双指针的算法小白~,这道题非常适合反复手推。
当真正理解“为什么移动短板”,双指针这类题就算真正入门了。