【LeetCode 热题 100】盛最多水的容器


精选专栏链接 🔗


欢迎订阅,点赞+关注,每日精进1%,与百万开发者共攀技术珠峰

更多内容持续更新中~



【LeetCode 热题 100】盛最多水的容器


📝题目描述

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, heighti) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量。

示例 1:

bash 复制代码
输入:[1,8,6,2,5,4,8,3,7]
输出:49 

解释: 图中垂直线代表输入数组 1,8,6,2,5,4,8,3,7。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

示例 2:

java 复制代码
输入:height = [1,1]
输出:1

💡提示信息

  • n == height.length;
  • 2 <= n <= 10 5 10^5 105;
  • 0 <= heighti <= 10 4 10^4 104;

核心分析:面积如何计算?

我们要找的是最大面积。假设我们选择了两条线,索引分别为 i i i 和 j j j(假设 i < j i < j i<j),它们的高度分别为 h e i g h t i heighti heighti 和 h e i g h t j heightj heightj

容器的面积公式为:
A r e a = 宽度 × 高度 Area = \text{宽度} \times \text{高度} Area=宽度×高度
A r e a = ( j − i ) × min ⁡ ( h e i g h t i , h e i g h t j ) Area = (j - i) \times \min(heighti, heightj) Area=(j−i)×min(heighti,heightj)

这意味着,容器的盛水量取决于两个因素:

  • 底边的宽度:两根柱子的距离;
  • 短板的高度:两根柱子中较短的那一根(木桶效应);

解法一:暴力枚举法

暴力枚举法是最直观的思路是,需要尝试所有的组合:

  • 外层循环从第 1 根柱子开始;
  • 内层循环遍历该柱子后面的所有柱子;
  • 计算每一对组合的面积,并更新最大值;

Java 代码实现

java 复制代码
class Solution {
    public int maxArea(int[] height) {
        int maxArea = 0;
        for(int i = 0; i<height.length; i++){
            for(int j = i + 1; j<height.length; j++){
                int width = j - i;
                int h = Math.min(height[i],height[j]);
                maxArea = Math.max(maxArea, width * h);
            }
        }
        return maxArea;
    }
}

提交代码,运行结果如下:

  • 时间复杂度: O ( N 2 ) O(N^2) O(N2)。我们需要计算 N ( N − 1 ) / 2 N(N-1)/2 N(N−1)/2 对组合;
  • 空间复杂度: O ( 1 ) O(1) O(1);

此方式逻辑上行得通,但是数组很大时,此方法会超时,因此不推荐使用,更推荐使用双指针法实现。


解法二:双指针法(最优解)

为了优化时间复杂度,我们需要一种更聪明的方法来排除不必要的计算。这就是双指针法

算法思路

  1. 初始化两个指针:left 指向数组头部(0),right 指向数组尾部(n-1)。此时宽度最大;
  2. 计算当前 leftright 围成的面积,并更新最大面积;
  3. 关键步骤: 移动指针。(移动较短的柱子
    • 如果 height[left] < height[right],则移动 leftleft++)。
    • 如果 height[left] >= height[right],则移动 rightright--)。
  4. 重复步骤 2 和 3,直到 leftright 相遇。

为什么移动较短的那根柱子?(数学证明)

这是本题最难理解也最精彩的地方。假设当前 height[left] < height[right]。此时面积 S = h e i g h t l e f t × ( r i g h t − l e f t ) S = heightleft \times (right - left) S=heightleft×(right−left)。

如果我们移动较高的柱子(即 right--):

  • 宽度 ( r i g h t − l e f t ) (right - left) (right−left) 必然减小;
  • 高度取决于 min ⁡ ( h e i g h t l e f t , h e i g h t n e w _ r i g h t ) \min(heightleft, heightnew\\_right) min(heightleft,heightnew_right)。因为高度受限于较短的 heightleft,无论 new_right多高,最终使用的高度都不可能超过 heightleft
  • 结论: 移动较高的柱子时,宽度变小,高度不变或变小 → \rightarrow → 面积一定变小

反之,如果我们移动较短的柱子(即 left++):

  • 宽度 ( r i g h t − l e f t ) (right - left) (right−left)必然减小;
  • 但是,新的高度 height[new_left] 可能会比原来的 height[left] 更高,从而可能抵消宽度的损失,甚至获得更大的面积。

一句话总结:只有移动短板,才有可能在宽度减小的情况下,通过增加高度来获得更大的面积。

Java 代码实现如下:

java 复制代码
public class Solution {
    public int maxArea(int[] height) {
        int left = 0;
        int right = height.length - 1;
        int maxArea = 0;

        while (left < right) {
            // 计算当前面积
            // 宽度是 right - left
            // 高度取两者的较小值
            int currentArea = Math.min(height[left], height[right]) * (right - left);
            
            // 更新最大面积
            maxArea = Math.max(maxArea, currentArea);

            // 移动指针策略:谁短移谁
            if (height[left] < height[right]) {
                left++;
            } else {
                right--;
            }
        }

        return maxArea;
    }
}

运行结果如下:

进一步优化代码:

java 复制代码
class Solution {
    public int maxArea(int[] height) {
        int max = 0;
        // 使用局部变量缓存数组长度
        int len = height.length;

        // 双指针
        int left = 0;
        int right = len - 1;

        while (left < right) {
            // 1. 缓存当前高度,避免重复访问数组
            int hLeft = height[left];
            int hRight = height[right];

            // 2. 计算宽度
            int width = right - left;

            // 3. 计算高度和移动指针
            if (hLeft < hRight) {
                // 左边矮,用左边算面积,左指针右移
                // 三元运算符替换原有 Math.min/Math.max
                int area = hLeft * width;
                max = area > max ? area : max;
                left++;
            } else {
                // 右边矮(或相等),用右边算面积,右指针左移
                int area = hRight * width;
                if (area > max) max = area;
                right--;
            }
        }
        return max;
    }
}

提交代码,运行结果如下:

复杂度分析

  • 时间复杂度: O ( N ) O(N) O(N)。双指针从两端向中间遍历,每个元素最多被访问一次。
  • 空间复杂度: O ( 1 ) O(1) O(1)。只需要常数级别的额外空间存储指针和最大面积。

总结

  • 暴力法虽然简单,但在大数据量下不可行;
  • 双指针法利用了"短板效应"这一特性,通过每次移动短板的策略,成功将 O ( N 2 ) O(N^2) O(N2) 优化到了 O ( N ) O(N) O(N);

希望这篇解析能帮你彻底掌握这道题!Happy Coding! 🚀

相关推荐
NE_STOP16 小时前
Vide Coding--AI编程工具的选择
java
通信小呆呆16 小时前
当算法有了“五感”:多模态数据融合如何向人体感官协同学习?
人工智能·学习·算法·机器学习·机器人
码云数智-园园16 小时前
C++20 Modules 模块详解
java·开发语言·spring
程序员黑豆16 小时前
JDK 下载安装与配置详细教程
java·前端·ai编程
霸道流氓气质16 小时前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
benben04417 小时前
强化学习之DQN算法族(基于gymnasium开发)
算法
小宇宙Zz17 小时前
Maven依赖冲突
java·服务器·maven
swordbob17 小时前
NIO的channel中什么是 fd(File Descriptor,文件描述符)
java·开发语言·nio
于先生吖17 小时前
SpringBoot对接大模型开发AI命理测算系统:八字排盘与AI解析接口源码全解
人工智能·spring boot·后端