【Hot100 |5-LeetCode 11. 盛最多水的容器】

LeetCode 11. 盛最多水的容器 - 双指针贪心解法详解

一、问题理解

问题描述

给定一个长度为 n 的整数数组 height,其中 height[i] 表示第 i 根柱子的高度。两根柱子与横轴组成一个"容器",容器的容量 = 两根柱子之间的距离(宽度) × 两根柱子中较矮的高度。

目标:找出数组中能容纳最多水的容器。

示例

text

输入: height = [1,8,6,2,5,4,8,3,7]
输出: 49
解释: 最大容量由索引 1(高度 8)和索引 8(高度 7)组成
      宽度 = 8 - 1 = 7,高度 = min(8, 7) = 7
      容量 = 7 × 7 = 49

二、核心思路:双指针 + 贪心策略

暴力解法 vs 双指针法

  • 暴力解法:遍历所有可能的柱子对 (i, j),计算每个容器的容量,时间复杂度为 O(n²)

  • 双指针法:通过贪心策略缩小搜索范围,时间复杂度优化到 O(n)

双指针贪心策略

  1. 双指针初始化

    • 左指针 left 从数组开头(索引 0)开始

    • 右指针 right 从数组末尾(索引 n-1)开始

    • 此时宽度最大,提供了初始的最大潜在容量

  2. 计算容量公式

    text

    容量 = 宽度 × 高度
         = (right - left) × min(height[left], height[right])
  3. 贪心移动策略

    • 每次移动较矮的柱子指针

    • 为什么移动较矮的柱子?

      • 如果移动较高的柱子:宽度减小,而高度最多还是由原来的矮柱决定,容量必然变小

      • 如果移动较矮的柱子:宽度减小,但可能遇到更高的柱子,从而可能增大容量

三、代码逐行解析

Java 解法

java

class Solution {
    public int maxArea(int[] height) {
        // 1. 初始化左指针(数组开头)
        int left = 0;
        // 2. 初始化右指针(数组末尾)
        int right = height.length - 1;
        // 3. 存储最大容量(初始为0)
        int maxArea = 0;
        
        // 4. 双指针循环:当左指针在右指针左侧时,继续缩小范围
        while (left < right) {
            // 5. 计算当前容器的宽度
            int width = right - left;
            // 6. 计算当前容器的高度(取较矮柱子)
            int minHeight = Math.min(height[left], height[right]);
            // 7. 计算当前容量
            int currentArea = width * minHeight;
            // 8. 更新最大容量
            maxArea = Math.max(maxArea, currentArea);
            
            // 9. 贪心移动指针:移动较矮的柱子
            if (height[left] < height[right]) {
                left++; // 左柱较矮,左指针右移
            } else {
                right--; // 右柱较矮(或等高),右指针左移
            }
        }
        
        // 10. 返回最大容量
        return maxArea;
    }
}

Python 解法

python

from typing import List

class Solution:
    def maxArea(self, height: List[int]) -> int:
        # 1. 初始化双指针
        left, right = 0, len(height) - 1
        # 2. 初始化最大容量
        max_area = 0
        
        # 3. 双指针循环
        while left < right:
            # 4. 计算当前宽度和高度
            width = right - left
            min_height = min(height[left], height[right])
            
            # 5. 计算当前容量并更新最大值
            current_area = width * min_height
            max_area = max(max_area, current_area)
            
            # 6. 贪心移动指针:移动较矮的柱子
            if height[left] < height[right]:
                left += 1  # 左柱较矮,左指针右移
            else:
                right -= 1  # 右柱较矮(或等高),右指针左移
        
        # 7. 返回最大容量
        return max_area

四、Java 与 Python 语法对比

1. 变量声明与初始化

操作JavaPython
变量声明int left = 0;left = 0
多变量声明int left = 0, right = n-1;left, right = 0, n-1
数组/列表长度height.lengthlen(height)

2. 循环与控制流

操作JavaPython
while 循环while (left < right) { ... }while left < right:
if-else 语句if (condition) { ... } else { ... }if condition: ... else: ...
递增/递减left++ / right--left += 1 / right -= 1

3. 数学运算与函数

操作JavaPython
最小值Math.min(a, b)min(a, b)
最大值Math.max(a, b)max(a, b)
绝对值Math.abs(num)abs(num)

五、实例演示

以测试用例 height = [1, 8, 6, 2, 5, 4, 8, 3, 7] 为例,演示双指针移动过程:

步骤leftright宽度高度当前容量最大容量移动指针(原因)
初始088min(1,7)=188left++ (左柱矮)
1187min(8,7)=74949right-- (右柱矮)
2176min(8,3)=31849right-- (右柱矮)
3165min(8,8)=84049right-- (等高,移动任意)
4154min(8,4)=41649right-- (右柱矮)
5143min(8,5)=51549right-- (右柱矮)
6132min(8,2)=2449right-- (右柱矮)
7121min(8,6)=6649right-- (右柱矮)
结束11---49left < right 不成立

最终结果:最大容量为 49

六、关键细节与复杂度分析

1. 为什么移动较矮的柱子是正确的?

假设当前左柱高度 < 右柱高度:

  • 如果移动右柱(较高的柱子):宽度减小,新高度 ≤ 左柱高度(因为左柱更矮),所以新容量必然小于当前容量

  • 如果移动左柱(较矮的柱子):宽度减小,但可能遇到更高的左柱,新高度可能增加,从而可能得到更大的容量

贪心选择证明:移动较矮的柱子不会错过最大容量解,因为任何包含当前矮柱的更大容量的容器都不可能存在(与当前高柱的组合已经计算过,与其他柱子的宽度更小,高度不会超过当前矮柱)。

2. 当两柱高度相等时,移动哪一侧?

代码中当 height[left] >= height[right] 时移动右指针,但实际上移动左指针也可以。因为两柱等高时:

  • 移动任何一侧,宽度都减小

  • 新高度最多等于当前高度(可能更小)

  • 因此新容量不会超过当前容量

关键点:必须移动指针才能继续循环,但移动哪一侧不影响最终结果。

3. 复杂度分析

  • 时间复杂度:O(n)

    • 每个指针从两端向中间移动,每个元素最多被访问一次

    • 总操作次数为 n-1

  • 空间复杂度:O(1)

    • 只使用了常数个额外变量(left, right, maxArea, currentArea等)

七、优化技巧与变体

1. 更简洁的 Python 写法

python

class Solution:
    def maxArea(self, height: List[int]) -> int:
        left, right = 0, len(height) - 1
        max_area = 0
        
        while left < right:
            # 直接计算并更新
            max_area = max(max_area, (right - left) * min(height[left], height[right]))
            
            # 移动较矮的柱子
            if height[left] < height[right]:
                left += 1
            else:
                right -= 1
        
        return max_area

2. 处理边界情况

python

class Solution:
    def maxArea(self, height: List[int]) -> int:
        # 处理空数组或单元素数组
        if len(height) < 2:
            return 0
        
        left, right = 0, len(height) - 1
        max_area = 0
        
        while left < right:
            # 计算当前容量
            width = right - left
            min_height = min(height[left], height[right])
            current_area = width * min_height
            
            # 更新最大容量
            if current_area > max_area:
                max_area = current_area
            
            # 贪心移动指针
            if height[left] < height[right]:
                left += 1
            else:
                right -= 1
        
        return max_area

3. 可视化理解(辅助代码)

python

def maxArea_visual(height):
    left, right = 0, len(height) - 1
    max_area = 0
    step = 0
    
    print("初始数组:", height)
    print("索引:    ", list(range(len(height))))
    
    while left < right:
        step += 1
        width = right - left
        min_height = min(height[left], height[right])
        current_area = width * min_height
        max_area = max(max_area, current_area)
        
        print(f"\n步骤 {step}:")
        print(f"  left={left}, right={right}")
        print(f"  高度: height[{left}]={height[left]}, height[{right}]={height[right]}")
        print(f"  宽度: {right} - {left} = {width}")
        print(f"  容量: {width} × {min_height} = {current_area}")
        print(f"  最大容量: {max_area}")
        
        if height[left] < height[right]:
            print(f"  移动左指针 (height[{left}]={height[left]} < height[{right}]={height[right]})")
            left += 1
        else:
            print(f"  移动右指针 (height[{left}]={height[left]} >= height[{right}]={height[right]})")
            right -= 1
    
    return max_area

# 测试
height = [1, 8, 6, 2, 5, 4, 8, 3, 7]
print("最终结果:", maxArea_visual(height))

八、常用函数积累

Java 常用函数

java

// 数组操作
int[] arr = {1, 2, 3};
int length = arr.length;          // 数组长度

// 数学运算
Math.min(a, b);                   // 返回最小值
Math.max(a, b);                   // 返回最大值
Math.abs(a);                      // 返回绝对值

// 循环与控制
for (int i = 0; i < n; i++) { }   // for循环
while (condition) { }             // while循环
if (condition) { } else { }       // if-else语句

Python 常用函数

python

# 列表操作
lst = [1, 2, 3]
length = len(lst)                 # 列表长度

# 内置函数
min(a, b)                         # 返回最小值
max(a, b)                         # 返回最大值
abs(a)                            # 返回绝对值

# 循环与控制
for i in range(n):                # for循环
while condition:                  # while循环
if condition:                     # if-else语句

九、总结

核心要点

  1. 双指针是核心:从数组两端开始,向中间移动

  2. 贪心策略是关键:总是移动较矮的柱子,以寻求更大的容量

  3. 时间复杂度优化:从暴力解法的 O(n²) 优化到 O(n)

面试常见问题

  1. 为什么这个解法是正确的?

    • 通过移动较矮的柱子,我们保留了找到更大容量的可能性,而不会错过最优解

  2. 当两柱高度相等时,移动哪一侧?

    • 移动任意一侧都可以,因为新容量不会超过当前容量

  3. 这个算法是否会错过最大容量?

    • 不会。算法的贪心选择保证了每次移动都是当前最优的,最终会找到全局最优解

  4. 如何处理空数组或单元素数组?

    • 空数组或单元素数组无法构成容器,应返回 0

  5. 这个算法适用于负数高度吗?

    • 题目假设高度为非负整数,但算法本身也适用于负数(不过实际意义不大)

扩展思考

  1. 类似问题:这种双指针从两端向中间移动的模式也适用于其他问题,如:

    • 两数之和 II(输入数组已排序)

    • 接雨水问题(需要更复杂的逻辑)

    • 验证回文串

  2. 变体问题:如果要求找到三个柱子形成的最大容量容器(三维容器),该如何解决?

    • 这需要更复杂的算法,可能涉及排序和双指针的组合

  3. 实际应用:盛水容器问题在计算几何和实际工程中都有应用,如:

    • 水库容量计算

    • 建筑设计中雨水收集系统设计

    • 图像处理中的区域检测

掌握双指针+贪心的解法思想,不仅能够解决这道LeetCode问题,还能够应用到其他许多需要高效搜索的问题中,是面试中非常重要的算法技巧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值