力扣hot100-------11、盛最多水的容器(java版)
1、题目描述
给定一个长度为
n的整数数组height。有n条垂线,第i条线的两个端点是(i, 0)和(i, height[i])。找出其中的两条线,使得它们与
x轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量。
说明:你不能倾斜容器。
示例 1:
输入:[1,8,6,2,5,4,8,3,7]输出:49 解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。示例 2:
输入:height = [1,1]输出:1
2、思路
一、思路分析:为什么用“双指针”?
1. 暴力法的缺陷
最直接的想法是:
枚举所有
(i, j)组合,计算面积,取最大值。
for (int i = 0; i < n; i++) {for (int j = i + 1; j < n; j++) {area = min(height[i], height[j]) * (j - i);maxArea = max(maxArea, area);}}正确性没问题。
时间复杂度 O(n²),当 n=10⁵ 时,操作次数 ≈ 50 亿 → 超时!
2. 优化方向:能不能“跳过”一些组合?
我们发现:
容器面积 = 宽度 × 高度
宽度 =
j - i高度 =
min(height[i],height[j])
面积受限于较短的那条边!
3. 双指针的核心思想(贪心 + 缩小搜索空间)
策略
初始化:
left = 0,right = n - 1(最大宽度)每次计算当前面积,更新最大值
移动“较短”的那根柱子的指针
为什么?因为:
如果你移动“较高的”那根 → 宽度减小,高度不会增加(甚至可能更小)→ 面积不可能变大
如果你移动“较短”的那根 → 宽度减小,但高度有可能变大 → 面积有可能变大!
理解:“宽度注定越来越小,我们只能赌高度能变大 —— 而只有移动短板,才有机会让‘决定高度的那根线’变高。移动长板,高度不会变高,面积只会更小 —— 所以果断放弃长板,搏一搏短板!”
这是一种“贪心策略”:每次放弃当前不可能产生更大面积的一侧,向内收缩,保留希望更大的一侧。
4. 为什么不会错过最优解?
这是最关键的问题!
证明思路(反证法):
假设真正的最大面积是由 i=a 和 j=b 形成的(a < b),但在双指针移动过程中,其中一个指针提前越过了 a 或 b。
但在移动过程中:
只有当某侧是“当前较短边”时才会移动它。
在
left=a, right=b时,一定会被计算一次!在那之前,如果
left或right被移动了,说明那一侧是“更短”的,而根据面积公式,它和任何中间柱子形成的面积都不可能比当前(a,b)更大 —— 所以提前移动它不会错过最优解!
所以,双指针法不会漏掉最大面积组合!
二、边界条件分析
while (left < right)循环条件:
left < right当
left == right时,宽度为 0,面积为 0 → 无需计算所以必须是严格小于
指针移动边界:
left最大移动到n-2(当 right = n-1)right最小移动到1(当 left = 0)永远不会越界,因为
left < right保证了left <= n-2,right >= 1
数组长度边界:
题目隐含
n >= 2(否则无法形成容器)但保险起见,可以加判断:
if (height == null || height.length < 2) return 0;三、完整 Java 代码 + 注释
class Solution {public int maxArea(int[] height) {// 边界判断:数组为空或长度不足2,无法构成容器if (height == null || height.length < 2) {return 0;}int left = 0; // 左指针int right = height.length - 1; // 右指针int maxArea = 0; // 最大面积// 双指针向中间收缩,直到相遇while (left < right) {// 计算当前容器面积:高度取短板,宽度为 right - leftint currentHeight = Math.min(height[left], height[right]);int currentWidth = right - left;int currentArea = currentHeight * currentWidth;// 更新最大面积maxArea = Math.max(maxArea, currentArea);// 移动较短边的指针,希望找到更高的边if (height[left] < height[right]) {left++; // 左边矮,移动左指针} else {right--; // 右边矮(或相等),移动右指针}}return maxArea;}}四、涉及的 Java 知识点讲解
1. 数组(Array)
int[] heightJava 中的数组是定长的,创建后长度不可变。
访问元素:
height[i],索引从0到length - 1获取长度:
height.length(注意不是方法,是属性)
2. Math 类的静态方法
Math.min(a, b)Math.max(a, b)Math是 Java 的工具类,提供数学运算方法。所有方法都是
static,直接用类名调用。min/max:比较两个数,返回较小/较大值。
3. while 循环控制
while (left < right) { ... }条件为真时重复执行循环体
每次循环必须让
left++或right--,否则会死循环循环结束条件明确:
left >= right
4. 变量作用域与初始化
int left = 0;int right = height.length - 1;int maxArea = 0;局部变量必须初始化后才能使用
Java 不允许使用未初始化的局部变量(编译报错)
5. 方法定义与返回值
public int maxArea(int[] height)public:访问修饰符,表示公开方法int:返回类型maxArea:方法名int[] height:参数,传入一个整型数组return maxArea;:返回计算结果
6. 条件语句 if-else
if (height[left] < height[right]) {left++;
} else {right--;
}用于二选一逻辑判断
注意:当两边相等时,移动哪边都可以(因为另一边也不会更大了)
五、算法复杂度总结
| 项目 | 说明 |
|---|---|
| 时间复杂度 | O(n) —— 只遍历一次 |
| 空间复杂度 | O(1) —— 只用几个变量 |
| 是否最优 | 是已知最优解 |
| 是否稳定 | 每次结果相同,无随机性 |
六、举个实际运行例子
输入:[1,8,6,2,5,4,8,3,7]
| 步骤 | left | right | 高度 | 宽度 | 面积 | maxArea | 移动哪边 |
|---|---|---|---|---|---|---|---|
| 1 | 0 | 8 | min(1,7)=1 | 8 | 8 | 8 | left++ |
| 2 | 1 | 8 | min(8,7)=7 | 7 | 49 | 49 | right-- |
| 3 | 1 | 7 | min(8,3)=3 | 6 | 18 | 49 | right-- |
| 4 | 1 | 6 | min(8,8)=8 | 5 | 40 | 49 | 任意 |
| 5 | 2 | 6 | min(6,8)=6 | 4 | 24 | 49 | left++ |
| ... | ... | ... | ... | ... | ... | 49 | ... |
最终结果:49
总结:为什么要用双指针?
| 原因 | 说明 |
|---|---|
| 时间效率高 | O(n) vs O(n²),大数据下性能差距巨大 |
| 空间效率高 | 只用常数空间,不依赖额外数据结构 |
| 思维巧妙 | 利用“短板效应” + “贪心移动”,每次排除不可能最优的一侧 |
| 面试高频考点 | 几乎所有大厂算法面试都会考,是“双指针”经典入门题 |
| 可扩展性强 | 思想可用于“接雨水”、“三数之和”、“有序数组两数之和”等经典问题 |

