LEETCODE 011Medium

盛最多水的容器

面积受制于短板,移动长板宽度变小、高度封顶,不可能更优——所以每次只动较短的一边。

问题拆解

数组里每个元素是一条竖线的高度,任选两条线和 x 轴围成容器,容积等于 min(height[i], height[j]) × (j - i),求最大容积。

枚举所有线对是 O(n²),对 10⁵ 的规模不可接受。想优化,得先看清容积公式的结构:宽度由两个下标的距离决定,高度由较短的那条线决定——长板再高也用不上,水会从短板一侧溢出。

于是从两端出发:left = 0right = n-1,此时宽度最大。接下来每一步必须收缩,宽度只会变小,想让面积翻盘只能指望高度变大。这时移动哪一边就有了明确答案:只能动短板。

收缩时宽度必然减小,而新容器的高度被留下的那条边封顶——留下短板,高度不可能超过它,面积只会更小;只有换掉短板才有翻盘的机会。

两端双指针,每次移动较短的一边

为什么“移动长板一定不优”值得说透。设当前 height[left] <= height[right],若移动 right,新容器的高度仍是 min(height[left], height[·]) <= height[left],即不超过原来的高度,宽度又严格变小,面积必定不增。也就是说,以当前短板 left 为一边的所有更窄组合都不可能超过当前值——当前面积已经是 left 能参与的最好成绩,可以放心让它退场。每一步都淘汰一条“已榨干潜力”的边,n-1 步后所有候选都被安全排除,最大值一定被记录过。

public int maxArea(int[] height) {
    int left = 0, right = height.length - 1;
    int best = 0;
    while (left < right) {
        int area = Math.min(height[left], height[right]) * (right - left);
        best = Math.max(best, area);
        if (height[left] <= height[right]) {
            left++; // 短板已无潜力,换掉它
        } else {
            right--;
        }
    }
    return best;
}
def maxArea(height: List[int]) -> int:
    left, right = 0, len(height) - 1
    best = 0

    while left < right:
        area = min(height[left], height[right]) * (right - left)
        best = max(best, area)
        if height[left] <= height[right]:
            left += 1  # 短板已无潜力,换掉它
        else:
            right -= 1

    return best
func maxArea(height []int) int {
    left, right := 0, len(height)-1
    best := 0
    for left < right {
        area := min(height[left], height[right]) * (right - left)
        best = max(best, area)
        if height[left] <= height[right] {
            left++ // 短板已无潜力,换掉它
        } else {
            right--
        }
    }
    return best
}
pub fn max_area(height: Vec<i32>) -> i32 {
    let (mut left, mut right) = (0usize, height.len() - 1);
    let mut best = 0;
    while left < right {
        let area = height[left].min(height[right]) * (right - left) as i32;
        best = best.max(area);
        if height[left] <= height[right] {
            left += 1; // 短板已无潜力,换掉它
        } else {
            right -= 1;
        }
    }
    best
}

两边等高时移动哪边都行:此时两条边互为短板,无论留哪条,更窄的组合高度都被它封顶,同样的论证对两侧都成立。容易踩的坑是把面积计算放在移动之后——必须先结算当前组合再收缩,否则初始的最宽组合会被漏掉。

复杂度

指标 复杂度 原因
时间 O(n) 两个指针合计移动 n-1 步,每步 O(1)
空间 O(1) 只有指针和当前最大值

可以迁移的模式

  • 双指针收缩的正确性论证套路:证明“被跳过的组合不可能是最优解”,而不是证明“留下的更好”;
  • 目标函数由多个因素相乘(宽 × 高)时,找到被谁“卡住”(短板),收缩就动谁;
  • 从两端最宽处出发、单向收缩,把 O(n²) 的组合枚举压成 O(n) 的淘汰过程。

双指针不是玄学:能用的前提是每一步都能安全排除一批候选,本题的短板论证就是范本。