【Hot100 |5-LeetCode 11. 盛最多水的容器】
这段代码是解决 LeetCode 11. 盛最多水的容器 问题的经典双指针贪心解法,核心目标是找到数组中能容纳最多水的 “容器”(由两个柱子和横轴组成的矩形),时间复杂度优化到 O (n),空间复杂度 O (1)。下面从问题理解→核心思路→代码逐行解析→实例演示四个维度详细讲解:
一、问题理解
问题要求
给定一个长度为 n 的整数数组 height,其中 height[i] 表示第 i 根柱子的高度。两根柱子与横轴组成一个 “容器”,容器的容量 = 两根柱子之间的距离(宽度) × 两根柱子中较矮的高度(高度)。要求找出数组中容量最大的容器。
例如:height = [1,8,6,2,5,4,8,3,7],最大容量由索引 1(高度 8)和索引 8(高度 7)组成,宽度为 7(8-1),高度为 7(min (8,7)),容量 = 7×7=49。
二、核心思路:双指针 + 贪心策略
暴力解法(遍历所有可能的两根柱子)的时间复杂度是 O (n²),而双指针法通过贪心缩小范围,实现线性时间复杂度,核心思路:
- 双指针初始化:左指针
left从数组开头(0)出发,右指针right从数组末尾(n-1)出发,此时宽度最大(初始潜在容量最大)。 - 计算当前容量:容量 = 宽度(
right - left) × 高度(min(height[left], height[right]),由矮柱决定)。 - 贪心移动指针:为了寻找更大的容量,需要移动指针缩小范围。移动较矮的柱子(因为:若移动较高的柱子,宽度减小,而高度最多还是由矮柱决定,容量必然变小;移动矮柱可能遇到更高的柱子,从而可能增大容量)。
- 更新最大容量:每次计算后,用当前容量更新全局最大容量
ans。
三、代码逐行解析
java
运行
class Solution {public int maxArea(int[] height) {// 1. 初始化左指针(数组开头)int left = 0;// 2. 初始化右指针(数组末尾)int right = height.length - 1;// 3. 临时存储当前容量int total = 0;// 4. 存储最大容量(初始为0)int ans = 0;// 5. 双指针循环:当左指针在右指针左侧时,继续缩小范围while (left < right) {// 6. 计算当前容器的容量:宽度×高度(高度取较矮柱子)total = (right - left) * Math.min(height[left], height[right]);// 7. 更新最大容量(取当前容量和历史最大的较大值)ans = Math.max(total, ans);// 8. 贪心移动指针:移动较矮的柱子,寻找更大的可能if (height[left] < height[right]) {left++; // 左柱较矮,左指针右移} else {right--; // 右柱较矮(或等高),右指针左移}}// 9. 返回最大容量return ans;}
}
四、实例演示(直观理解过程)
以测试用例 height = [1,8,6,2,5,4,8,3,7] 为例,演示双指针移动和容量计算过程:
| 步骤 | left | right | 宽度 (right-left) | 高度 (min (左,右)) | 当前容量 (total) | 最大容量 (ans) | 移动指针(原因) |
|---|---|---|---|---|---|---|---|
| 初始 | 0 | 8 | 8 | min(1,7)=1 | 8×1=8 | 8 | 左柱矮(1<7)→ left=1 |
| 1 | 1 | 8 | 7 | min(8,7)=7 | 7×7=49 | 49 | 右柱矮(7<8)→ right=7 |
| 2 | 1 | 7 | 6 | min(8,3)=3 | 6×3=18 | 49 | 右柱矮(3<8)→ right=6 |
| 3 | 1 | 6 | 5 | min(8,8)=8 | 5×8=40 | 49 | 等高(8=8)→ 右指针左移(right=5) |
| 4 | 1 | 5 | 4 | min(8,4)=4 | 4×4=16 | 49 | 右柱矮(4<8)→ right=4 |
| 5 | 1 | 4 | 3 | min(8,5)=5 | 3×5=15 | 49 | 右柱矮(5<8)→ right=3 |
| 6 | 1 | 3 | 2 | min(8,2)=2 | 2×2=4 | 49 | 右柱矮(2<8)→ right=2 |
| 7 | 1 | 2 | 1 | min(8,6)=6 | 1×6=6 | 49 | 右柱矮(6<8)→ right=1 |
| 结束 | 1 | 1 | 不满足 left<right,循环结束 | - | - | 49 | - |
最终结果:ans=49,与预期一致。
五、关键细节与复杂度分析
1. 为什么移动较矮的柱子能找到最大容量?
假设当前左柱高度 < 右柱高度:
- 若移动右柱(较高的一边),新的宽度减小,且新的高度最多还是左柱高度(因为左柱更矮),因此新容量必然小于当前容量。
- 若移动左柱(较矮的一边),虽然宽度减小,但可能遇到更高的左柱,新高度可能增大,从而可能得到更大的容量。因此,移动较矮的柱子是 “贪心” 的最优选择,不会错过潜在的更大容量。
2. 当两柱高度相等时,移动哪一侧?
代码中选择移动右指针(else { right--; }),但实际上移动左指针也可以。因为两柱等高时,无论移动哪一侧,新的容量都不可能超过当前容量(宽度减小,高度最多不变),但必须移动才能继续缩小范围,不影响最终结果。
3. 复杂度分析
- 时间复杂度:O (n)。左右指针从两端向中间移动,每个元素最多被访问一次,总操作次数为 n。
- 空间复杂度:O (1)。仅使用了
left、right、total、ans4 个变量,无额外空间开销。
六、总结
该解法的核心是双指针 + 贪心策略:通过初始最大宽度的容器开始,每次移动较矮的柱子缩小范围,在 O (n) 时间内高效找到最大容量。这种 “从两端向中间收缩,通过局部最优选择逼近全局最优” 的思路,也是解决类似 “区间最值” 问题的经典思想,在面试中高频考察。
