LeetCode 11 盛最多水的容器

点此看全部题解 LeetCode必刷100题:一份来自面试官的算法地图(题解持续更新中)

生活中的算法

你有没有遇到过这样的场景:家里要举办派对,需要准备一个大容器来调制果汁。面前有很多不同高度的挡板,你需要选择两个,前后固定住,这样就能盛放饮料了。挡板的高度不同,间距也可以调整,你要怎么选择才能盛放最多的饮料呢?

这就是我们今天要讲的"盛最多水的容器"问题。本质上,我们要在一排不同高度的"墙"中选择两堵墙,使它们之间能容纳最多的水。

问题描述

LeetCode第11题"盛最多水的容器"是这样描述的:给定一个长度为n的整数数组height,有n条垂线,第i条线的两个端点是(i, 0)和(i, height[i])。找出其中的两条线,使得它们与x轴共同构成的容器可以容纳最多的水。

比如,输入height = [1,8,6,2,5,4,8,3,7],最大容积是49(由height[1]=8和height[8]=7构成,宽度为7)。

最直观的解法:暴力枚举法

最容易想到的方法就是:尝试所有可能的容器组合,找出能盛水最多的那个。就像我们实际准备容器时,可能会挨个试一试每种组合。

具体步骤是这样的:

  1. 使用两层循环,遍历所有可能的左右边界组合
  2. 对每个组合,计算其能容纳的水量
  3. 更新最大水量

让我们用一个小例子来模拟这个过程:

height = [1,8,6,2]

尝试所有组合:
(0,1): min(1,8) * 1 = 1
(0,2): min(1,6) * 2 = 2
(0,3): min(1,2) * 3 = 3
(1,2): min(8,6) * 1 = 6
(1,3): min(8,2) * 2 = 4
(2,3): min(6,2) * 1 = 2 最大容积 = 6

这种思路可以用Java代码这样实现:

public int maxArea(int[] height) {
int maxVolume = 0; // 遍历所有可能的容器组合
for (int i = 0; i < height.length; i++) {
for (int j = i + 1; j < height.length; j++) {
// 计算当前组合的容积
// 容积 = 两边较矮一边的高度 * 两边的距离
int volume = Math.min(height[i], height[j]) * (j - i);
maxVolume = Math.max(maxVolume, volume);
}
} return maxVolume;
}

优化解法:双指针法

仔细思考,我们其实不需要尝试所有组合。关键是理解:容器的容积取决于两个因素:

  1. 两边中较短的那条边(决定了水的高度)
  2. 两边的距离(决定了水的宽度)

如果我们从最宽的容器开始,逐渐向内收缩,每次都移动较短的那条边,就一定不会错过最大容积。

双指针法的原理

为什么这样做是对的?假设我们有两个指针left和right:

  1. 容积由较短的边决定
  2. 如果移动较长的边,宽度减小,而高度最多只能是较短边的高度
  3. 所以移动较长的边,容积一定会减小
  4. 但移动较短的边,可能会找到一个更高的边,容积可能增大

算法步骤(伪代码)

  1. 初始化左右指针指向数组两端
  2. 当左右指针未相遇时:
    • 计算当前容积
    • 更新最大容积
    • 移动较短的那条边的指针
  3. 返回最大容积

示例运行

让我们用一个例子[1,8,6,2,5,4,8,3,7]模拟这个过程:

初始状态:left=0(高度1), right=8(高度7)
容积=min(1,7)*8=8,移动left left=1(高度8), right=8(高度7)
容积=min(8,7)*7=49,移动right left=1(高度8), right=7(高度3)
容积=min(8,3)*6=18,移动right ...依此类推

Java代码实现

public int maxArea(int[] height) {
int maxVolume = 0;
int left = 0;
int right = height.length - 1; while (left < right) {
// 计算当前容积
int width = right - left;
int currentHeight = Math.min(height[left], height[right]);
int volume = width * currentHeight; // 更新最大容积
maxVolume = Math.max(maxVolume, volume); // 移动较短的一边
if (height[left] < height[right]) {
left++;
} else {
right--;
}
} return maxVolume;
}

暴力法vs双指针法

让我们比较这两种解法:

暴力法的时间复杂度是O(n²),需要遍历所有可能的组合。它的优点是直观易懂,适合用来理解问题。但在处理大规模数据时效率较低。

双指针法的时间复杂度是O(n),只需要遍历一次数组。它通过巧妙的证明,保证了不会错过最优解,同时大大提高了效率。

两种方法的空间复杂度都是O(1),因为只需要常数级的额外空间。

题目模式总结

这道题体现了两个重要的算法思想:

  1. 双指针技巧:通过移动两个指针来解决问题
  2. 贪心思想:每次都移动较短的边,期望找到更好的解

这种模式在很多问题中都有应用,比如:

  • 两数之和(排序数组)
  • 三数之和
  • 接雨水

解决这类问题的通用思路是:

  1. 观察问题中的单调性质
  2. 寻找可以优化的搜索策略
  3. 证明优化策略的正确性

小结

通过这道题,我们不仅学会了如何找到能盛最多水的容器,更重要的是理解了如何用双指针技巧来优化搜索过程。这种思维方式在很多算法问题中都能派上用场。

记住,解决算法问题时,不要满足于暴力解法,多思考是否存在更优雅的方案。有时候,看似复杂的问题,找到正确的思路后,解法会变得异常简单!


作者:忍者算法
公众号:忍者算法

从倒水问题到盛最多水的容器:一道经典的双指针应用题|LeetCode 11 盛最多水的容器的更多相关文章

  1. LeetCode 11. 盛最多水的容器(Container With Most Water)

    题目描述 给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) .画 n 条垂直线,使得垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0).找出其中的两 ...

  2. Java实现 LeetCode 11 盛最多水的容器

    11. 盛最多水的容器 给定 n 个非负整数 a1,a2,-,an,每个数代表坐标中的一个点 (i, ai) .在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) ...

  3. 力扣Leetcode 11. 盛最多水的容器

    盛最多水的容器 给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) .在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0).找 ...

  4. Leetcode 11.盛最多水的容器 By Python

    给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) .在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0).找出其中的两条线, ...

  5. LeetCode 11 - 盛最多水的容器 - [双指针暴力]

    题目链接:https://leetcode-cn.com/problems/container-with-most-water/description/ 给定 n 个非负整数 $a_1,a_2,\cd ...

  6. [LeetCode]11. 盛最多水的容器(双指针)

    题目 给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) .在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0).找出其中的两 ...

  7. leetcode 11盛水最多的容器

    class Solution { public: int maxArea(vector<int>& height) { //双指针法:从最宽的容器开始计算,当更窄的容器盛水量要大于 ...

  8. LeetCode:盛最多水的容器【11】

    LeetCode:盛最多水的容器[11] 题目描述 给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) .在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为  ...

  9. 11.07图论水题Test

    11.07图论水题Test 题目 描述 做法 \(BSOJ6378\) 在\(i\)位置可以到\(i+a_i\)或\(i+b_i\)求\(1\rightarrow n\)字典序最小路径 判可达性后贪心 ...

  10. 【LeetCode】盛最多水的容器【双指针+贪心 寻找最大面积】

    给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) .在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0).找出其中的两条线, ...

随机推荐

  1. context之WithTimeout的使用

    1. context包的WithTimeout()函数接受一个 Context 和超时时间作为参数,返回其子Context和取消函数cancel2. 新创建协程中传入子Context做参数,且需监控子 ...

  2. An Entry Example of Log4j

    The log4j can be configured both programmatically and externally using special configuration files. ...

  3. Java深度历险(三)——Java线程​:基本概念、可见性与同步

    开发高性能并发应用不是一件容易的事情.这类应用的例子包括高性能Web服务器.游戏服务器和搜索引擎爬虫等.这样的应用可能需要同时处理成千上万个请求.对于这样的应用,一般采用多线程或事件驱动的架构.对于J ...

  4. npm之基本使用

    # 查看镜像源 npm config get registry # 设置镜像源 # 腾讯云 npm config set registry http://mirrors.cloud.tencent.c ...

  5. python之日志记录loguru

    安装: pip install loguru 基础使用: from loguru import logger logger.debug("This is a debug...") ...

  6. 【第2章】matlab程序设计基础

    matlab语言的常量与变量 matlab语言的变量命名规则 由一个字母引导,后面可以为其他字符. 区分大小写 如Abc ≠ ABc matlab的保留常量 以下为系统保留常量,自己定义的变量不能与他 ...

  7. 鼠标事件:mouseout、mouseover事件会不断触发

    mouseover 和 mouseenter mouseenter不会冒泡,而mouseover会冒泡 mouseover:指针进入事件监听的元素内 或者 其他的子元素内 都会触发mouseover ...

  8. Spring完全注解开发

    注解的好处:如果管理很多的Bean,要求这些Bean都配置在applocationContext.xml文件中.用了注解之后,就不需要在xml文件中配置了,Spring提供了几个辅助类会自动扫描和装配 ...

  9. 【C#】【平时作业】习题-7-继承、抽象与多态

    相关概念 什么是继承 继承定义了如何根据现有类创先新类的过程 任何类都可以从另外一个类继承 一个派生出来的子类具有这个类的所有公共属性和方法 类的继承机制 创建新类所根据的基础类称为基类或父类,新建的 ...

  10. 使用docker-compose快速部署Prometheus+grafana环境

    由于最近公司服务频繁出问题,老板很生气,下面的人都很不好过,于是老大让加一下业务监控,来观察线上数据状态.但是由于qa环境数据量太少,所以自己搭建了一套环境做相关监控,并且写了个脚本模仿生产上的数据, ...