在刷 LeetCode 的过程中,「盛最多水的容器」是一道绕不开的经典题。
它几乎是双指针思想的代表作,不仅出现频率高,而且非常考察对问题本质的理解。
这篇笔记我会围绕一个核心点展开:
为什么双指针是对的?为什么只移动短板?
一、题目回顾
给定一个数组 height,其中:
height[i]表示第i根竖线的高度- 每两根竖线与 x 轴可以围成一个容器
容器能装水的面积公式是:
area = (right - left) * min(height[left], height[right])
目标很简单:
找到能装水最多的一对竖线,返回最大面积
二、暴力解法为什么行不通?
最直观的想法是:
- 枚举所有
(i, j) - 计算面积
- 取最大值
代码好写,但时间复杂度是:
O(n²)
当数组规模上来之后,这种解法必然超时。
所以关键问题是:
能不能在不枚举所有组合的情况下,直接逼近最优解?
三、双指针的核心思想
1. 为什么从两端开始?
双指针的起点是:
left = 0
right = n - 1
原因只有一个:
一开始,容器的宽度最大
而面积 = 宽度 × 高度
要想得到最大面积,宽度不能一开始就浪费掉。
2. 当前面积由谁决定?
每一步我们都会计算:
area = (right - left) * min(height[left], height[right])
注意一个关键事实:
面积永远由“较短的那根线”决定
这就是后续指针移动策略的根本依据。
四、为什么只移动短板?
这是这道题的灵魂问题。
假设当前状态是:
height[left] < height[right]
那么:
- 当前面积被
height[left]限制 - 即使右边再高,也没用
如果此时你选择:
移动右指针
- 宽度变小
- 高度上限仍然是
height[left] - 面积只会变小或不变
移动左指针
- 宽度变小
- 但有可能遇到更高的左边竖线
- 面积才有机会变大
结论只有一个:
哪边短,就移动哪边
这是一个非常典型的贪心策略。
五、双指针完整流程
算法流程可以概括为四步:
- 左右指针从数组两端出发
- 计算当前容器面积,更新最大值
- 比较左右高度
- 移动较短的那一侧指针
一直重复,直到左右指针相遇。
六、Java 实现代码
这是面试和刷题中最标准、最推荐的写法:
class Solution {
public int maxArea(int[] height) {
int left = 0;
int right = height.length - 1;
int max = 0;
while (left < right) {
int h = Math.min(height[left], height[right]);
int area = h * (right - left);
max = Math.max(max, area);
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return max;
}
}
七、复杂度分析
时间复杂度:
O(n)
空间复杂度:
O(1)
这也是这道题真正优秀的地方:
用极低的成本完成了对解空间的压缩。
八、这道题真正想考什么?
这道题并不是单纯考你会不会写双指针,而是考:
- 能不能识别“面积由短板决定”
- 能不能证明某些情况一定不可能成为最优解
- 是否具备用指针移动来“剪枝”的能力
如果你能清楚解释一句话:
为什么只移动短的那一边?
那这道题在面试里就已经过关了。
九、总结一句话
盛最多水的容器,本质不是穷举所有可能,
而是通过双指针 + 贪心思想,不断淘汰不可能成为最优解的情况。
这是一道非常值得反复回看的经典题。