盛最多水的容器:从"暴力拆家"到"双指针魔法"的奇妙之旅
🌊 问题初体验:当柱子开始"装水"
想象一下,你面前有9根柱子,高度分别是:[1, 8, 6, 2, 5, 4, 8, 3, 7](就像一排整齐的"水桶支架")。你的任务是:找出两根柱子,让它们和地面围成的"水桶"装最多的水。
目标:找出两个柱子,使它们与地面围成的容器盛水最多(面积 = 两柱间距 × 较短柱子高度)。
关键约束:柱子宽度为1(即间距 = 索引差)。
示例验证:
索引1(高8)和索引8(高7) → 面积 = (8-1) × min(8,7) = 49
这是最大值(其他组合均 ≤49)。
🔍 解法一:暴力枚举(O(n²))——简单但效率低下
思路:遍历所有可能的柱子对(i, j),计算面积,取最大值。
代码:
function maxArea(height) {
let max = 0;
for (let i = 0; i < height.length; i++) {
for (let j = i + 1; j < height.length; j++) {
const area = (j - i) * Math.min(height[i], height[j]);
max = Math.max(max, area);
}
}
return max;
}
弊端分析:
-
时间复杂度 O(n²) :当
n = 10⁵时,计算量达 10¹⁰ 次。 -
实际影响:在LeetCode测试用例中,
n=10⁵时必然超时(典型超时阈值为1-2秒,10¹⁰次计算需100+秒)。 -
为什么幼稚?
“就像在10000个文件夹里,手动检查每一份文件是否包含‘重要’二字——逻辑正确,但效率堪忧。”
🧭 解法二:双指针法(O(n))——核心优化
核心思想:
宽度最大时,短板决定高度;移动短板,才能突破高度瓶颈。
为什么移动短板?
-
初始时,左右指针在两端(宽度最大,
width = n-1)。 -
若
height[left] < height[right](左短板),则:- 移动左指针:宽度减1,但高度可能增加(新左柱可能更高)。
- 不移动右指针:宽度已减,高度上限仍是左短板(无法突破)。
代码:
function maxArea(height) {
let max = 0;
let left = 0;
let right = height.length - 1;
while (left < right) {
const width = right - left;
const minHeight = Math.min(height[left], height[right]);
max = Math.max(max, width * minHeight);
// 移动短板(关键!)
if (height[left] <= height[right]) {
left++;
} else {
right--;
}
}
return max;
}
效率验证:
-
时间复杂度 O(n) :指针从两端向中间移动,每根柱子最多访问1次。
-
n=10⁵时:计算量仅 10⁵ 次(1秒内完成)。
-
为什么高效?
“如同在图书馆找最薄的书(短板),从书架两端同时扫描:左边的书比右边薄,就移左端——因为右边的书(长板)无法提供更高‘书架’。”
⚡ 解法三:双指针优化(跳过连续矮柱,O(n))——进一步减少冗余
优化点:移动短板时,跳过所有比当前短板矮的柱子(因为它们无法提供更大面积)。
逻辑验证:
- 当前短板高度为
min,所有比min矮的柱子,与当前短板配对的面积必然 ≤ 当前面积(宽度已减小,高度更矮)。 - 直接跳过这些矮柱子,直到遇到更高柱子。
代码:
function maxArea(height) {
let max = 0;
let left = 0;
let right = height.length - 1;
while (left < right) {
const minHeight = Math.min(height[left], height[right]);
max = Math.max(max, minHeight * (right - left));
// 跳过连续矮柱(仅移动短板一侧)
while (left < right && height[left] <= minHeight) left++;
while (left < right && height[right] <= minHeight) right--;
}
return max;
}
为什么有效?
-
时间复杂度仍为 O(n) :每根柱子最多被访问1次(跳过操作不增加额外循环)。
-
实际优化:若左侧有连续5根矮柱,可一次性跳过(而非逐个移动),减少操作次数。
-
对比解法二:
- 解法二:移动1步 → 1次操作
- 解法三:移动5步 → 1次操作(减少4次无效移动)
关键提示:此优化非必需(解法二已足够高效),但能小幅提升实际运行速度(尤其在柱子高度分布不均时)。
📊 三种解法对比(专业总结)
| 解法 | 时间复杂度 | 代码简洁度 | 实际效率 | 适用场景 |
|---|---|---|---|---|
| 暴力枚举 | O(n²) | ⭐⭐ | 极差 | 仅用于 n < 100 的教学演示 |
| 双指针(标准) | O(n) | ⭐⭐⭐ | 优秀 | 通用场景,面试首选 |
| 双指针(跳过矮柱) | O(n) | ⭐⭐⭐ | 略优 | 高度分布稀疏的优化场景 |
💎 核心结论:为什么双指针是解题关键?
-
暴力枚举:逻辑直白但不可行(O(n²) 空间换时间,代价过高)。
-
双指针(标准) :
- 本质:用宽度最大化(初始时)+ 短板移动(动态调整)来避免无效计算。
- 证明:假设当前最优解为
(i, j),若height[i] < height[j],则i无法作为左边界(因i之后的左边界宽度更小,高度上限 ≤height[i])。
-
优化版:非必须,但体现了算法设计的“贪心”思想(跳过不可能解)。
面试点睛:
“面试官问‘如何优化?’,答出‘移动短板’即可拿满分——跳过矮柱是锦上添花,但核心是理解‘短板瓶颈’。”
✅ 最终建议
-
面试/实战:直接使用双指针标准解法(解法二),简洁高效。
-
避免陷阱:不要纠结于“跳过矮柱”(易写错逻辑),标准解法已足够。
-
一句话总结:
“宽度最大时,短板决定高度;移动短板,才能突破瓶颈——这是盛水容器问题的黄金法则。”