【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. 计算当前 left 和 right 围成的面积,并更新最大面积;
  3. 关键步骤: 移动指针。(移动较短的柱子 )
    • 如果 height[left] < height[right],则移动 left(left++)。
    • 如果 height[left] >= height[right],则移动 right(right--)。
  4. 重复步骤 2 和 3,直到 left 和 right 相遇。

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

这是本题最难理解也最精彩的地方。假设当前 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! 🚀

相关推荐
yi0115 分钟前
DAY17: LeetCode 139|单词拆分:从“把单词删掉”到用 DP 记录合法切口
人工智能·笔记·python·算法·leetcode·动态规划
马剑威(威哥爱编程)8 分钟前
【AI全栈后端12-03】Spring Boot 用多模型路由把智能客服成本降下来
java·人工智能·spring boot
霸道流氓气质13 分钟前
AI模型幻觉检测与抑制完全指南:从规则引擎到RAG对比的Java生产级实战
java·开发语言·人工智能
陈希瑞18 分钟前
LongCat-2.5-Preview 的web逆向能力实测
人工智能·算法·wasm
niucloud-admin23 分钟前
JAVA V6 多商户商城 开发文档——手机端前端
java·开发语言·前端
云安全助手29 分钟前
自建接入VS聚合平台:企业 AI 调用的选型思路与迁移成本拆解
java·大数据·数据库·人工智能·ai大模型
樱花落木兰29 分钟前
SpringBoot + ECharts 后台数据统计报表模块实战
java·javascript·spring boot·ai·log4j·github·echarts
霸道流氓气质38 分钟前
Dify 可视化 LLM 应用开发平台完全指南:从Workflow编排到Java生产级集成实战
java·开发语言
m0_5873830043 分钟前
24小时自助健身房软硬件解决方案实战:从架构设计到部署指南
java·spring·小程序·架构·需求分析
CallFay云起未来1 小时前
AI客服上线后多久才能回本?从TCO到ROI的完整测算方法
java·大数据·人工智能·架构·文心一言