LeetCode 11. 盛最多水的容器

5 阅读5分钟

图解算法:为什么一定要移动那个短板?| LeetCode 11. 盛最多水的容器

前言:在面试中,有一类题目看似简单,暴力解法也能做,但面试官真正想看的是你如何将 

O(N2)

 的复杂度优化到 

O(N)

。LeetCode 11 题“盛最多水的容器”就是这类题目的典范。今天我们不背代码,而是深入探讨背后的贪心策略双指针思维。

一、 题目直觉与“木桶效应”

题目的目标非常直观:在一个数组中找到两条垂线,使得它们与 X 轴围成的容器能盛最多的水。

我们要计算的是矩形面积:

Area=Width×HeightArea=Width×Height

这里有一个物理常识至关重要,那就是木桶效应 (Short Board Effect)
一个木桶能装多少水,取决于最短的那块木板。

映射到题目中:

  • 宽度 (Width) :两条垂线在 X 轴上的距离 right - left。
  • 高度 (Height) :两条垂线中较矮的那一条,即 Math.min(height[left], height[right])。

二、 痛点:为什么暴力解法不行?

最容易想到的思路是双重循环:计算所有两两组合的面积,然后取最大值。

然而以我的经验,当你写下双循环的时候,你自己心中的无奈,没有人会比你更了解

面试官在了解到你的解题思路时,就已经将你pass掉了

任何算法题,写双循环的结果只有死路一条(因为他会认为你对空间与时间复杂度没有概念,或者你的实力就这么多)

JavaScript

//  暴力解法
let max = 0;
for (let i = 0; i < len; i++) {
    for (let j = i + 1; j < len; j++) {
        // 计算每一对组合...
    }
}

这种解法的时间复杂度是

O(N2)


题目提示中数组长度 

NN

 可达 

105105

。这意味着计算量高达 

10101010

 次。在通常的算法竞赛或面试标准中,这绝对会触发 TLE (Time Limit Exceeded)  超时错误。

我们需要一种更聪明的做法,将复杂度降维打击到

O(N)

三、 核心:双指针法与贪心策略

我们要优化的核心是:如何尽可能少地遍历,却能保证不漏掉最大值?

1. 初始布局:拉满宽度

既然面积 = 宽 × 高,我们不妨先让宽度最大
我们在数组的头尾各放置一个指针:left 指向开头,right 指向结尾。

此时,容器的底宽是最大的。接下来的每一步移动,宽度必然减小。为了弥补宽度的损失,我们必须寻找更高的垂线。

2. 决策困境:移动哪一根?

这是本题最难理解的点。假设现在的状况是:

  • 左边柱子高度 left_h = 2

  • 右边柱子高度 right_h = 8

  • 当前宽度 w = 10

  • 当前面积 = 

    10=202×10=20
    

现在我们需要向内移动一个指针,是移左边的(矮的),还是移右边的(高的)?

假设我们移动高的那一边(右边):

宽度肯定变小了(变成 9)。
而水位高度取决于谁?依然是左边那个不动的短板(高度 2)。
无论右边新遇到的柱子是高耸入云还是矮小不堪,容器的有效高度最高只能是 2

新面积=9×min⁡(2,新高度)≤18新面积=9×min(2,新高度)≤18

结论:  移动高板,宽度减小,高度受限于不动的短板(无法增加)。面积只会变小,绝对不可能变大。  这是一条死路。

贪心策略:移动矮的那一边(左边):

虽然宽度变小了(变成 9),但我们抛弃了当前的短板(高度 2)。
如果运气好,左边新遇到的柱子高度是 10,那么新的有效高度就变成了 8(受限于右边)。

新面积=9×8=72新面积=9×8=72

结论:  只有移动短板,我们才有可能找到更高的柱子来弥补宽度的损失。

这就是本题的贪心逻辑:  每一步我们都排除掉那个“导致当前高度受限”的短板,因为它已经发挥了它的最大潜力(在当前最宽的情况下),保留它没有任何意义。

四、 代码实现

理解了上述逻辑,代码实现就非常简单了。

JavaScript

/**
 * @param {number[]} height
 * @return {number}
 */
var maxArea = function(height) {
    // 1. 定义双指针,分别指向头尾
    let left = 0;
    let right = height.length - 1;
    let maxWater = 0;
    
    // 2. 当指针未相遇时循环
    while (left < right) {
        // 3. 计算当前面积
        // 高度取决于短板 (木桶效应)
        const currentHeight = Math.min(height[left], height[right]);
        const currentWidth = right - left;
        
        // 更新历史最大值
        maxWater = Math.max(maxWater, currentHeight * currentWidth);
        
        // 4. 核心决策:移动较矮的一侧
        // 如果左边是短板,那左边这块板子在当前宽度下已经发挥了最大价值,
        // 再往里缩宽度只会变小,保留左边没意义,不如向右移试试看有没有更高的。
        if (height[left] < height[right]) {
            left++;
        } else {
            right--;
        }
    }
    
    return maxWater;
};

五、 复杂度分析

  • 时间复杂度:

    O(N)
    

    双指针 left 和 right 总共遍历整个数组一次。相比于暴力解法的 

    O(N2)
    

    ,效率提升是巨大的。

  • 空间复杂度:

    O(1)O(1) 
    

    我们只需要存储指针索引和 maxWater 几个变量,不需要额外的数组空间。

六、 总结

所谓算法优化,往往不是代码写得有多复杂,而是思维模型的转换

LeetCode 11 题通过观察“木桶效应”,让我们明白:保留长板、抛弃短板是唯一可能获得更大收益的路径。这种通过排除法将搜索空间从二维矩阵(所有组合)压缩到一维线性扫描(双指针)的过程,就是算法中的降维打击

希望这篇文章能帮你彻底搞懂双指针解法!