11. 盛最多水的容器
问题描述
给定一个长度为 n
的整数数组 height
,表示 n
条垂线的高度。找出两条垂线,使得它们与 x
轴共同构成的容器能容纳最多的水,返回最大容量。
示例:
输入:height = [1,8,6,2,5,4,8,3,7]
输出:49
解释:垂线 1(高度 8)和垂线 8(高度 7)形成的容器容量为 7 * 7 = 49(表示为蓝色部分)
算法思路
双指针法
:
- 指针初始化:
- 左指针
left
指向数组起始位置 - 右指针
right
指向数组末尾位置
- 左指针
- 容量计算:
- 当前容量 =
min(height[left], height[right]) * (right - left)
- 当前容量 =
- 指针移动规则:
- 移动高度较小的指针(因为容量受限于较小高度)
- 目标:寻找更高的垂线以提升潜在容量
- 更新最大容量:每次计算后更新全局最大值
正确性:
- 每次移动较小高度的指针,保留了获得更大容量的可能性
- 不会错过最优解,因为容量由较小高度决定
- 时间复杂度 O(n),空间复杂度 O(1)
代码实现
class Solution {
public int maxArea(int[] height) {
int left = 0; // 左指针
int right = height.length - 1; // 右指针
int maxArea = 0; // 最大容量
while (left < right) {
// 计算当前容量
int currentHeight = Math.min(height[left], height[right]);
int width = right - left;
int area = currentHeight * width;
// 更新最大容量
maxArea = Math.max(maxArea, area);
// 移动较小高度的指针
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return maxArea;
}
}
代码注释
代码部分 | 说明 |
---|---|
left = 0 | 左指针初始位置 |
right = height.length - 1 | 右指针初始位置 |
Math.min(height[left], height[right]) | 容器有效高度 |
right - left | 容器宽度 |
area = currentHeight * width | 计算当前容量 |
Math.max(maxArea, area) | 更新全局最大容量 |
height[left] < height[right] ? left++ : right-- | 移动较小高度的指针 |
算法过程
height = [1,8,6,2,5,4,8,3,7]
:
- 初始状态:
left=0 (h=1)
,right=8 (h=7)
- 容量 =
min(1,7)*8 = 8
→maxArea=8
- 移动左指针(高度较小)
- 容量 =
- 步骤1:
left=1 (h=8)
,right=8 (h=7)
- 容量 =
min(8,7)*7 = 49
→maxArea=49
- 移动右指针
- 容量 =
- 步骤2:
left=1 (h=8)
,right=7 (h=3)
- 容量 =
min(8,3)*6 = 18
→maxArea=49
- 移动右指针
- 容量 =
- 继续直至指针相遇,最终返回
49
复杂度分析
- 时间复杂度:O(n)
只需遍历数组一次 - 空间复杂度:O(1)
仅使用常数空间
关键点
- 双指针移动原理:
- 容量受限于较小高度,移动较小指针可能获得更大容量
- 移动较大指针必然导致容量减小(宽度减小,高度不变或更小)
贪心策略
:- 每次移动都保留获得更大容量的可能性
- 不会错过最优解
- 高效性:
- 只需一次扫描,无需额外空间
- 比暴力法 O(n²) 显著优化
测试用例
public static void main(String[] args) {
Solution solution = new Solution();
// 示例测试
int[] height1 = {1,8,6,2,5,4,8,3,7};
System.out.println(solution.maxArea(height1)); // 49
// 均匀高度测试
int[] height2 = {5,5,5,5,5};
System.out.println(solution.maxArea(height2)); // 20 (5*4)
// 递增高度测试
int[] height3 = {1,2,3,4,5};
System.out.println(solution.maxArea(height3)); // 6 (min(2,5)*3=6)
// 递减高度测试
int[] height4 = {5,4,3,2,1};
System.out.println(solution.maxArea(height4)); // 6 (min(5,1)*4=4 → min(5,2)*3=6)
// 边界测试
int[] height5 = {1,1};
System.out.println(solution.maxArea(height5)); // 1
}
常见问题
-
为什么移动较小高度的指针?
因为容量受限于较小高度。移动较小指针可能遇到更高垂线,从而增加容量潜力;而移动较大指针必然导致宽度减少,且高度不会增加(因为容量受限于较小高度)。 -
是否会错过最优解?
不会。假设最优解为(i,j)
,当指针移动到这对组合时,必然已经通过移动较小高度的策略到达该位置。 -
如何处理高度相等的情况?
当height[left] == height[right]
时,移动任意指针均可(因为无论移动哪边,宽度都减少1,但另一边高度不变,容量必然减小)。 -
最坏情况下的性能如何?
最坏情况需要扫描整个数组(如单调递增/递减数组),时间复杂度 O(n),仍优于暴力法 O(n²)。