盛最多水的容器 图解题解
两端往中间夹,每次只移矮的那根——矮板是瓶颈,留着它宽度还会缩,只移矮的才有机会翻盘。
两个人各举一块挡板站在数轴两端,往中间走,每步只挪矮的那个——因为短板决定水位,把高的挪进来只会让宽度缩小又不提水位,只有挪矮的才有机会换到更高的板子把水量翻盘上去。
这道题到底在问什么
- height
- [1, 8, 6, 2, 5, 7]
- 输出
- 28 (第 2 条 8 和第 6 条 7,宽 4)
先想最直接的笨办法
枚举所有线对,n² 个组合,n 一大就慢,而且大量组合明显没希望还白算。换个思路:从「最宽」的一对开始,每步只放弃一条注定没希望的线。(动画第 3 步)
最优解:一步一步想明白
- 3枚举所有线对,n² 个组合,n 一大就慢,而且大量组合明显没希望还白算。换个思路:从「最宽」的一对开始,每步只放弃一条注定没希望的线。
- 4l、r 在最左最右,此时宽度最大。面积由矮的那条决定,所以移走矮的——因为留着它、宽度还只会变小,面积只会更差;只有移走它,才可能换到更高的线、把面积翻盘上去。这就是从暴力到双指针的关键转折。
- 5l=0, r=5, best=0左指针 l=0、右指针 r=5,从最宽的一对开始。best 先记 0,准备一路刷新。
- 6min(1,7)×5当前面积 = min(1,7)×5 = 5,刷新 best=5。两条里左边的 1 是矮的,它就是瓶颈。
- 7左 1 较矮 → l 右移左边 1 比右边 7 矮,移走它(l 右移)。注意不是看面积大小,而是看谁矮就移谁。
- 8min(8,7)×4l 来到 8:面积 = min(8,7)×4 = 28,刷新 best=28!这一步就是最终答案。被丢掉的 1(灰)以后不再看。
- 9右 7 较矮 → r 左移这回左边 8 高、右边 7 矮,所以移走右边(r 左移)。移矮边的判据每步重新看。
- 10min(8,5)×3 = 15 小于 28负例分支:面积 = min(8,5)×3 = 15,比 best 还小,best 不更新。但右边 5 仍是矮的,必须照样移走它——不能因为面积变小就停或回头,宽度只会越来越小,留着矮边没有未来。
- 11min(8,2)×2 = 4min(8,2)×2 = 4,更小,best 仍是 28。右边 2 矮,继续 r 左移。
- 12min(8,6)×1 = 6,l=1 r=2 相邻现在 l=1、r=2 紧挨着。面积 min(8,6)×1 = 6。右边 6 矮,移它:r 左移到 1,正好和 l 重合,循环停。全程 best 最大是 28,对应一开始那对 8 和 7。
- 15面积受限于矮的一边,留着它宽度还在缩、注定更差;放弃矮的才有翻盘机会——这是对撞双指针里的贪心选择。
- 17错法:移走较高的左边 8反例真跑一遍:到了 l=1(8)、r=5(7),正确该移矮的右边、面积 28 已记下。可一旦记成「移高的」、把 8 移走,l 跳到 6,宽度还缩到 3,28 这对再也凑不出来。这就是为什么判据必须是「移矮」。
- 20学会「两端收缩、每步移走拖后腿的一端」后,去同类的 三数之和:同样左右指针对撞,只是把「移矮」换成「按和的大小移」。再难一档是接雨水。也可以直接问小欧「11 和 15 的双指针判据有什么不同」。
⚠️ 容易写错的地方
✗ 错:哪边面积小就移哪边 / 移走较高的一边
✓ 对:永远移走较矮的一边
判据是「谁矮移谁」,不是「面积大小」。面积由矮边决定,移高边宽度还变小、必更差,会直接错过 28 这种解
✗ 错:面积用较高的高度
✓ 对:用 min(左, 右) 高度
水从矮的一边溢出,取高边会把面积算大
✗ 错:while l <= r
✓ 对:while l < r
l == r 时宽度为 0、装不了水,应停下
完整代码(Python / C++ / Java)
Python
class Solution:
def maxArea(self, height):
l, r = 0, len(height) - 1
ans = 0
while l < r:
ans = max(ans, (r - l) * min(height[l], height[r]))
if height[l] < height[r]:
l += 1
else:
r -= 1
return ansC++
class Solution {
public:
int maxArea(vector<int>& height) {
int l = 0, r = height.size() - 1, ans = 0;
while (l < r) {
ans = max(ans, (r - l) * min(height[l], height[r]));
if (height[l] < height[r]) l++;
else r--;
}
return ans;
}
};Java
class Solution {
public int maxArea(int[] height) {
int l = 0, r = height.length - 1, ans = 0;
while (l < r) {
ans = Math.max(ans, (r - l) * Math.min(height[l], height[r]));
if (height[l] < height[r]) l++;
else r--;
}
return ans;
}
}套路模板 · 对撞双指针「移走限制瓶颈」(背下来)
# 两端向中间收、每步放弃"拖后腿"的一边
l, r, best = 0, n - 1, 初值
while l < r:
best = 更新(best, 用 l, r 算的值)
if 谁是瓶颈 == 左: l += 1
else: r -= 1// 骨架:填 init / measure / 哪边是瓶颈
int l = 0, r = n - 1, best = INIT;
while (l < r) {
best = combine(best, measure(l, r));
if (bottleneckIsLeft(l, r)) l++; // 放弃左
else r--; // 否则放弃右
}// 骨架:填 init / measure / 哪边是瓶颈
int l = 0, r = n - 1, best = INIT;
while (l < r) {
best = combine(best, measure(l, r));
if (bottleneckIsLeft(l, r)) l++; // 放弃左
else r--; // 否则放弃右
}复杂度
时间复杂度
O(n)
l、r 一头一尾相向,每步必有一个指针前进一格、从不回头,加起来恰好走 n 步
空间复杂度
O(1)
只用 l、r、ans 几个变量
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 盛最多水的容器 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么移动矮的那条指针?+
面积受限于矮线;移高线只会让宽度变小、瓶颈不变、面积必减。移矮线才有机会换到更高的线。
双指针为什么不会漏掉答案?+
每次移矮指针,舍弃的是「以这条矮线为边界的所有更窄组合」,它们面积都不可能更大,所以不漏最优。
两条线一样高时移哪个?+
移哪个都行、不漏解。这两条配出的最大面积已经在当前这步算过;之后无论保留哪边,配更窄的线只会让面积变小,所以丢任意一条都安全。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。