盛最多水的容器:从"暴力拆家"到"双指针魔法"的奇妙之旅

46 阅读4分钟

盛最多水的容器:从"暴力拆家"到"双指针魔法"的奇妙之旅

🌊 问题初体验:当柱子开始"装水"

想象一下,你面前有9根柱子,高度分别是:[1, 8, 6, 2, 5, 4, 8, 3, 7](就像一排整齐的"水桶支架")。你的任务是:找出两根柱子,让它们和地面围成的"水桶"装最多的水

image.png

目标:找出两个柱子,使它们与地面围成的容器盛水最多(面积 = 两柱间距 × 较短柱子高度)。
关键约束:柱子宽度为1(即间距 = 索引差)。

示例验证
索引1(高8)和索引8(高7) → 面积 = (8-1) × min(8,7) = 49
这是最大值(其他组合均 ≤49)。


🔍 解法一:暴力枚举(O(n²))——简单但效率低下

思路:遍历所有可能的柱子对(i, j),计算面积,取最大值。
代码

function maxArea(height) {
    let max = 0;
    for (let i = 0; i < height.length; i++) {
        for (let j = i + 1; j < height.length; j++) {
            const area = (j - i) * Math.min(height[i], height[j]);
            max = Math.max(max, area);
        }
    }
    return max;
}

弊端分析

  • 时间复杂度 O(n²) :当 n = 10⁵ 时,计算量达 10¹⁰ 次。

  • 实际影响:在LeetCode测试用例中,n=10⁵ 时必然超时(典型超时阈值为1-2秒,10¹⁰次计算需100+秒)。

  • 为什么幼稚?

    “就像在10000个文件夹里,手动检查每一份文件是否包含‘重要’二字——逻辑正确,但效率堪忧。”


🧭 解法二:双指针法(O(n))——核心优化

核心思想

宽度最大时,短板决定高度;移动短板,才能突破高度瓶颈

为什么移动短板?

  • 初始时,左右指针在两端(宽度最大,width = n-1)。

  • 若 height[left] < height[right](左短板),则:

    • 移动左指针:宽度减1,但高度可能增加(新左柱可能更高)。
    • 不移动右指针:宽度已减,高度上限仍是左短板(无法突破)。

代码

function maxArea(height) {
    let max = 0;
    let left = 0;
    let right = height.length - 1;
    
    while (left < right) {
        const width = right - left;
        const minHeight = Math.min(height[left], height[right]);
        max = Math.max(max, width * minHeight);
        
        // 移动短板(关键!)
        if (height[left] <= height[right]) {
            left++;
        } else {
            right--;
        }
    }
    return max;
}

效率验证

  • 时间复杂度 O(n) :指针从两端向中间移动,每根柱子最多访问1次。

  • n=10⁵时:计算量仅 10⁵ 次(1秒内完成)。

  • 为什么高效?

    “如同在图书馆找最薄的书(短板),从书架两端同时扫描:左边的书比右边薄,就移左端——因为右边的书(长板)无法提供更高‘书架’。”


⚡ 解法三:双指针优化(跳过连续矮柱,O(n))——进一步减少冗余

优化点:移动短板时,跳过所有比当前短板矮的柱子(因为它们无法提供更大面积)。

逻辑验证

  • 当前短板高度为 min,所有比 min 矮的柱子,与当前短板配对的面积必然 ≤ 当前面积(宽度已减小,高度更矮)。
  • 直接跳过这些矮柱子,直到遇到更高柱子。

代码

function maxArea(height) {
    let max = 0;
    let left = 0;
    let right = height.length - 1;
    
    while (left < right) {
        const minHeight = Math.min(height[left], height[right]);
        max = Math.max(max, minHeight * (right - left));
        
        // 跳过连续矮柱(仅移动短板一侧)
        while (left < right && height[left] <= minHeight) left++;
        while (left < right && height[right] <= minHeight) right--;
    }
    return max;
}

为什么有效?

  • 时间复杂度仍为 O(n) :每根柱子最多被访问1次(跳过操作不增加额外循环)。

  • 实际优化:若左侧有连续5根矮柱,可一次性跳过(而非逐个移动),减少操作次数。

  • 对比解法二

    • 解法二:移动1步 → 1次操作
    • 解法三:移动5步 → 1次操作(减少4次无效移动)

关键提示:此优化非必需(解法二已足够高效),但能小幅提升实际运行速度(尤其在柱子高度分布不均时)。


📊 三种解法对比(专业总结)

解法时间复杂度代码简洁度实际效率适用场景
暴力枚举O(n²)⭐⭐极差仅用于 n < 100 的教学演示
双指针(标准)O(n)⭐⭐⭐优秀通用场景,面试首选
双指针(跳过矮柱)O(n)⭐⭐⭐略优高度分布稀疏的优化场景

💎 核心结论:为什么双指针是解题关键?

  1. 暴力枚举:逻辑直白但不可行(O(n²) 空间换时间,代价过高)。

  2. 双指针(标准)

    • 本质:用宽度最大化(初始时)+ 短板移动(动态调整)来避免无效计算
    • 证明:假设当前最优解为 (i, j),若 height[i] < height[j],则 i 无法作为左边界(因 i 之后的左边界宽度更小,高度上限 ≤ height[i])。
  3. 优化版非必须,但体现了算法设计的“贪心”思想(跳过不可能解)。

面试点睛
“面试官问‘如何优化?’,答出‘移动短板’即可拿满分——跳过矮柱是锦上添花,但核心是理解‘短板瓶颈’。”


✅ 最终建议

  • 面试/实战:直接使用双指针标准解法(解法二),简洁高效。

  • 避免陷阱:不要纠结于“跳过矮柱”(易写错逻辑),标准解法已足够。

  • 一句话总结

    “宽度最大时,短板决定高度;移动短板,才能突破瓶颈——这是盛水容器问题的黄金法则。”