盛最多水的容器
面积受制于短板,移动长板宽度变小、高度封顶,不可能更优——所以每次只动较短的一边。
问题拆解
数组里每个元素是一条竖线的高度,任选两条线和 x 轴围成容器,容积等于 min(height[i], height[j]) × (j - i),求最大容积。
枚举所有线对是 O(n²),对 10⁵ 的规模不可接受。想优化,得先看清容积公式的结构:宽度由两个下标的距离决定,高度由较短的那条线决定——长板再高也用不上,水会从短板一侧溢出。
于是从两端出发:left = 0、right = 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)的淘汰过程。
双指针不是玄学:能用的前提是每一步都能安全排除一批候选,本题的短板论证就是范本。