思路分析
容器的水量 = 「两条线的水平距离」×「两条线中较矮的那条的高度」(短板决定容量)。
暴力枚举所有两条线的组合(O (n²))会超时,最优解法用** 双指针贪心策略** :
- 初始指针 left 指向数组开头(0),right 指向数组末尾(n-1),此时水平距离最大;
- 计算当前容器水量,更新最大水量;
- 贪心移动指针:始终移动指向较矮线条的指针(因为移动高线条只会让水平距离减小,而容量由短板决定,无法得到更大水量;移动矮线条可能遇到更高线条,从而提升容量);
- 重复步骤 2-3,直到 left >= right。
代码实现
java
public int maxArea(int[] height){
// 定义双指针,left指向数组的第一个元素,right指向数组的最后一个元素
int left = 0, right = height.length - 1;
// 定义maxArea记录最大面积
int maxArea = 0;
// 遍历数组,更新maxArea
while(left < right){
// 计算当前面积
int curArea = (right - left) * Math.min(height[left], height[right]);
// 更新maxArea
maxArea = Math.max(maxArea, curArea);
// 更新指针
if (height[left] < height[right]){
left++;
}else{
right--;
}
}
return maxArea;
}
复杂度分析
- 时间复杂度:O (n),仅遍历数组一次,双指针移动总次数为 n;
- 空间复杂度:O (1),仅使用常数额外空间。
为什么这种方式可以
核心结论:双指针通过「贪心策略+排除无效解」,无需遍历所有情况(双层for循环),就能保证找到最大值------因为它每一步都在主动淘汰不可能成为最优解的组合,只保留有潜力的候选,本质是"用逻辑减少无效枚举"。
要理解这个问题,我们先拆透两个关键:
一、先想透:为什么双层for循环能得到答案,但没必要?
双层for循环的核心是「枚举所有两条线的组合」,计算每个组合的水量,再取最大值。这确实能得到正确答案,因为它覆盖了所有可能性。
但问题在于效率,所以,我们需要一种"不枚举所有组合,但能找到最大值"的方法,而双指针就是基于「贪心+排除法」实现的。
二、双指针的核心逻辑:为什么"移动短板"能排除无效解?
我们先明确两个前提:
- 容器水量 = 水平距离 × 短板高度(短板是容量的"天花板");
- 初始时,双指针在数组两端(left=0,right=n-1),此时水平距离最大------这是所有组合中"距离最长"的候选,是一个重要的基准。
接下来关键思考:当我们计算完当前组合(left, right)的水量后,该移动哪个指针?为什么移动短板,而不是长板?
举个例子:假设当前left指向的高度是3,right指向的高度是5(短板是left)。
- 如果移动长板(right左移):新的组合是(left, right-1)。此时水平距离减少了1,而短板依然是left(3)(因为right-1的高度无论大于3还是小于3,短板都不会比3更大)。所以新组合的水量 = (距离-1)× 短板(≤3),必然小于原来的水量(距离×3)------这个新组合是"无效解",不可能成为最大值,没必要枚举。
- 如果移动短板(left右移):新的组合是(left+1, right)。此时水平距离减少了1,但短板可能变大(比如left+1的高度是4),新水量 = (距离-1)×4,有可能比原来的水量更大------这个组合是"有潜力的解",需要保留。
简单说:移动长板只会让水量变小,移动短板才有可能让水量变大。所以每一步移动短板,本质是在"淘汰所有以当前短板为左/右边界的无效组合",这些组合没必要再用双层for循环枚举了。
三、为什么双指针能覆盖所有"有潜力的最优解"?
很多人担心:双指针跳过了很多组合,会不会漏掉最大值?
答案是:不会。因为被跳过的组合,全是"不可能比当前已知最大值更大"的无效解。
我们用一个形象的比喻理解:
- 所有可能的组合,都可以看作是"以不同left和right为边界的矩形";
- 初始时,我们有一个"最宽的矩形"(left=0,right=n-1),这是第一个候选;
- 之后每一步,我们都在"缩小宽度",但通过"移动短板",试图让 高度的提升抵消宽度的减少------从而得到更大的面积;
- 被跳过的组合(比如移动长板得到的组合),** 都是"宽度变小,高度还不变/变小"的矩形 **,它们的面积必然小于当前候选,不可能成为最大值。
举个具体例子(数组[1,8,6,2,5,4,8,3,7]):
- 初始组合(0,8):水量=8×1=8(短板是0);
- 移动短板(left=0→1):新组合(1,8),水量=7×7=49(当前最大值);
- 此时短板是right=8(高度7),移动right=8→7:新组合(1,7),水量=6×3=18(小于49);
- 继续移动right=7→6:新组合(1,6),水量=5×8=40(仍小于49);
- 后续所有组合的水量,都无法超过49------因为要么宽度更小,要么短板更低。
整个过程中,双指针只遍历了n次(O(n)),但每一步都在"锁定更优的候选",没有漏掉任何可能成为最大值的组合。
四、总结:双指针 vs 双层for循环
| 对比维度 | 双层for循环 | 双指针 |
|---|---|---|
| 核心逻辑 | 枚举所有组合,暴力比较 | 贪心移动短板,排除无效解 |
| 时间复杂度 | O(n²)(超时风险) | O(n)(高效) |
| 是否漏解 | 不会(覆盖所有情况) | 不会(只淘汰无效解) |
| 本质区别 | "穷举所有"找最大值 | "逻辑筛选"找最大值 |
简单说:双层for循环是"大海捞针",双指针是"精准定位"------它用贪心策略证明了"很多针根本没必要捞",从而在不牺牲正确性的前提下,大幅提升效率。
这也是算法设计的核心思想:不是所有问题都需要"遍历所有情况",只要能通过逻辑排除无效解,就能用更高效的方法找到答案。