Files
interview/16-LeetCode Hot 100/盛最多水的容器.md
yasinshaw 58b7491868 fix: remove remaining Java implementation sections
- Remove all "### Java 实现" sections from all markdown files
- Clean up remaining Java code blocks and references
- Ensure only Go code remains in all documentation
- Complete the Java to Go migration for all 22 files
2026-03-05 12:32:09 +08:00

375 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 盛最多水的容器 (Container With Most Water)
## 题目描述
给定一个长度为 `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**
```
输入:[1,1]
输出1
```
### 约束条件
- `n == height.length`
- `2 <= n <= 10^5`
- `0 <= height[i] <= 10^4`
## 解题思路
### 方法一:双指针法(最优解)
**核心思想:**使用两个指针,一个在数组开头,一个在数组末尾。每次移动较短的指针向中间靠拢。
**为什么这样做?**
- 容器的容量由 `min(height[left], height[right]) * (right - left)` 决定
- 如果移动较高的指针,宽度减小,高度只能保持不变或减小,容量一定不会增大
- 如果移动较短的指针,虽然宽度减小,但可能会找到更高的线,从而增大容量
**算法步骤:**
1. 初始化 `left = 0``right = len(height) - 1``maxArea = 0`
2.`left < right` 时:
- 计算当前面积:`area = min(height[left], height[right]) * (right - left)`
- 更新 `maxArea = max(maxArea, area)`
- 如果 `height[left] < height[right]`,则 `left++`,否则 `right--`
3. 返回 `maxArea`
### 方法二:暴力枚举(不推荐)
枚举所有可能的线对,计算每对线构成的容器容量,取最大值。时间复杂度 O(n²),会超时。
## 代码实现
### Go 实现
```go
package main
import (
"fmt"
)
func maxArea(height []int) int {
left, right := 0, len(height)-1
maxArea := 0
for left < right {
// 计算当前面积
width := right - left
h := height[left]
if height[right] < h {
h = height[right]
}
area := width * h
// 更新最大面积
if area > maxArea {
maxArea = area
}
// 移动较短的指针
if height[left] < height[right] {
left++
} else {
right--
}
}
return maxArea
}
// 测试用例
func main() {
// 测试用例1
height1 := []int{1, 8, 6, 2, 5, 4, 8, 3, 7}
fmt.Printf("输入: %v\n", height1)
fmt.Printf("输出: %d\n", maxArea(height1)) // 期望输出: 49
// 测试用例2
height2 := []int{1, 1}
fmt.Printf("\n输入: %v\n", height2)
fmt.Printf("输出: %d\n", maxArea(height2)) // 期望输出: 1
// 测试用例3: 递增序列
height3 := []int{1, 2, 3, 4, 5}
fmt.Printf("\n输入: %v\n", height3)
fmt.Printf("输出: %d\n", maxArea(height3)) // 期望输出: 6
// 测试用例4: 递减序列
height4 := []int{5, 4, 3, 2, 1}
fmt.Printf("\n输入: %v\n", height4)
fmt.Printf("输出: %d\n", maxArea(height4)) // 期望输出: 6
// 测试用例5: 包含0
height5 := []int{0, 2}
fmt.Printf("\n输入: %v\n", height5)
fmt.Printf("输出: %d\n", maxArea(height5)) // 期望输出: 0
}
```
- **时间复杂度:** O(n)
- 只需遍历数组一次,每次移动一个指针
- 指针最多移动 n 次
- **空间复杂度:** O(1)
- 只使用了常数级别的额外空间
- 只需要几个变量存储指针和最大面积
### 暴力枚举
- **时间复杂度:** O(n²)
- 需要枚举所有可能的线对
- 共有 n(n-1)/2 种组合
- **空间复杂度:** O(1)
- 只需要常数级别的额外空间
## 进阶问题
### Q1: 如果可以倾斜容器,问题会如何变化?
**A:** 如果可以倾斜容器,问题会变得复杂得多。需要考虑水的倾斜角度和容器的几何形状,这涉及到更多的物理和几何计算。
### Q2: 如果需要返回构成最大容器的两条线的索引,应该如何修改?
**A:** 在更新 `maxArea` 的同时,记录当前的 `left``right` 索引。
```go
// Go 版本
func maxAreaWithIndex(height []int) (int, int, int) {
left, right := 0, len(height)-1
maxArea, bestL, bestR := 0, 0, 0
for left < right {
h := height[left]
if height[right] < h {
h = height[right]
}
area := (right - left) * h
if area > maxArea {
maxArea = area
bestL, bestR = left, right
}
if height[left] < height[right] {
left++
} else {
right--
}
}
return maxArea, bestL, bestR
}
```
### Q3: 如果数组中有负数,应该如何处理?
**A:** 如果高度可以为负数,需要先过滤掉负数或取绝对值。通常物理意义上的高度不应为负,但如果题目允许,可以这样处理:
```go
// 处理负数:取绝对值
func maxAreaWithNegative(height []int) int {
left, right := 0, len(height)-1
maxArea := 0
for left < right {
h := height[left]
if height[right] < h {
h = height[right]
}
h = abs(h) // 取绝对值
area := (right - left) * h
maxArea = max(maxArea, area)
if abs(height[left]) < abs(height[right]) {
left++
} else {
right--
}
}
return maxArea
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
```
## P7 加分项
### 1. 深度理解:为什么双指针法一定正确?
**数学证明:**
假设当前指针在 `left``right`,且 `height[left] < height[right]`
我们要证明:移动 `right` 指针一定不会得到更大的面积。
- 当前面积:`S1 = height[left] * (right - left)`
- 移动 `right` 后的面积:`S2 = min(height[left], height[right-1]) * (right - 1 - left)`
- 由于 `height[left] < height[right]`,且 `right-1 < right`
- 所以 `S2 <= height[left] * (right - 1 - left) < height[left] * (right - left) = S1`
因此,移动较高的指针不会得到更大的面积。
### 2. 实战扩展:接雨水问题 (Trapping Rain Water)
**LeetCode 42:** 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
```go
func trap(height []int) int {
if len(height) < 3 {
return 0
}
left, right := 0, len(height)-1
leftMax, rightMax := 0, 0
water := 0
for left < right {
if height[left] < height[right] {
if height[left] >= leftMax {
leftMax = height[left]
} else {
water += leftMax - height[left]
}
left++
} else {
if height[right] >= rightMax {
rightMax = height[right]
} else {
water += rightMax - height[right]
}
right--
}
}
return water
}
```
**核心区别:**
- 盛水容器:找两条线构成最大面积
- 接雨水:计算所有能接的雨水总量
### 3. 变形题目
#### 变形1盛最多水的容器 II允许倾斜
如果允许容器倾斜,最大水量取决于两条线之间的最小距离和角度。
#### 变形2三维盛水
给定一个 m × n 的矩阵,每个格子表示高度,找出能盛最多水的四个角构成的容器。
#### 变形3动态盛水
容器的高度会随时间变化,求某个时间段内能盛的最大水量。
### 4. 优化技巧
#### 优化1提前终止
如果当前可能的面积(即使宽度最大)已经小于 `maxArea`,可以提前终止。
```go
func maxAreaOptimized(height []int) int {
left, right := 0, len(height)-1
maxArea := 0
for left < right {
area := (right - left) * min(height[left], height[right])
maxArea = max(maxArea, area)
// 提前终止如果当前宽度已经很窄可能无法超过maxArea
if right-left <= maxArea/max(height[left], height[right]) {
break
}
if height[left] < height[right] {
left++
} else {
right--
}
}
return maxArea
}
```
#### 优化2跳过明显不可能的线
如果移动后的线高度比移动前还低,可以继续移动直到找到更高的线。
```go
func maxAreaSkip(height []int) int {
left, right := 0, len(height)-1
maxArea := 0
for left < right {
area := (right - left) * min(height[left], height[right])
maxArea = max(maxArea, area)
if height[left] < height[right] {
oldLeft := left
left++
// 跳过比oldLeft还低的线
for left < right && height[left] <= height[oldLeft] {
left++
}
} else {
oldRight := right
right--
// 跳过比oldRight还低的线
for left < right && height[right] <= height[oldRight] {
right--
}
}
}
return maxArea
}
```
### 5. 实际应用场景
- **水库设计:** 计算水库的最大蓄水量
- **城市规划:** 确定建筑物之间的最佳距离以最大化绿化面积
- **数据压缩:** 在某些压缩算法中寻找最优的分段点
### 6. 面试技巧
**面试官可能会问:**
1. "为什么双指针法一定能找到最优解?"
2. "如果数组有 10^8 个元素,你的算法还能用吗?"
3. "如何证明你的算法是正确的?"
**回答要点:**
1. 给出数学证明(如上所述)
2. 讨论算法的局限性,考虑分布式处理
3. 提供正确的证明思路
### 7. 相关题目推荐
- LeetCode 42: 接雨水
- LeetCode 11: 盛最多水的容器(本题)
- LeetCode 84: 柱状图中最大的矩形
- LeetCode 85: 最大矩形