力扣hot100之11:盛最多水的容器
力扣hot100之11:盛最多水的容器

LeetCode 11:盛最多水的容器(Container With Most Water)题解

Container With Most Water 是 LeetCode Hot 100 里面一道非常经典的双指针题。

这道题看起来像是一道简单的数组题,但实际上它非常适合用来理解 双指针贪心移动 的核心思想。很多人第一次做这道题时,都会先想到暴力枚举:把每两个位置都试一遍,算出它们能装多少水,再取最大值。这个思路当然没问题,但时间复杂度太高,最终会发现这道题真正想考察的,其实是你对 双指针优化 的理解。

本文就结合你这份 Java 代码,系统地讲一下这道题的解法。


题目介绍

给定一个长度为 n 的整数数组 height

数组中的每个元素代表一根竖线的高度,第 i 根竖线的两个端点分别是:

  • (i, 0)

  • (i, height[i])

现在需要你从中选出两根竖线,与 x 轴一起构成一个容器。

这个容器所能盛水的面积由下面这个公式决定:

面积 = 宽度 × 高度
     = (right - left) × min(height[left], height[right])

其中:

  • leftright 是两根竖线的位置

  • 宽度就是它们之间的距离

  • 高度由两根竖线中较短的那一根决定,因为水位不可能超过短板

题目要求返回这个容器最多可以盛多少水。


示例

示例 1

输入:height = [1,8,6,2,5,4,8,3,7]
输出:49

为什么答案是 49

因为选择下标 1 和下标 8 这两根柱子时:

  • 宽度:8 - 1 = 7

  • 高度:min(8, 7) = 7

所以面积为:

7 × 7 = 49

示例 2

输入:height = [1,1]
输出:1

只有两根柱子,宽度是 1,高度是 1,面积自然就是 1


最直接的思路:暴力枚举

如果按照最朴素的想法,我们可以枚举所有的两根柱子:

  • 第一根柱子位置是 i

  • 第二根柱子位置是 j

  • 计算面积 (j - i) * min(height[i], height[j])

  • 最后取最大值

伪代码大概是这样:

for (int i = 0; i < n; i++) {
    for (int j = i + 1; j < n; j++) {
        ans = Math.max(ans, (j - i) * Math.min(height[i], height[j]));
    }
}

这个方法当然能做出来,但时间复杂度是:

O(n^2)

当数组长度比较大时,这种做法效率就不够高了。

所以这道题更优的做法,是 双指针


双指针解法

这道题最经典的思路就是:

  • 用两个指针,一个放在最左边 left

  • 一个放在最右边 right

  • 每次先计算当前这两个位置形成的面积

  • 然后移动较短的那一边

你给出的代码就是这个标准写法:

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

        while (left < right) {
            int currentArea = (right - left) * Math.min(height[left], height[right]);
            maxArea = Math.max(maxArea, currentArea);

            if (height[left] < height[right]) left++;
            else right--;
        }

        return maxArea;
    }
}

这段代码很短,但背后的思想非常重要。


为什么要从两端开始?

因为容器面积由两个因素决定:

  1. 两根柱子之间的距离

  2. 两根柱子中较短的高度

如果我们一开始把指针放在数组两端,那么这时候的宽度是最大的。

虽然当前的高度不一定理想,但至少宽度已经拉满了。之后我们再通过不断移动指针,尝试寻找更高的短板,看看能不能在宽度变小的同时,让高度提升更多,从而得到更大的面积。

所以“双指针从两端向中间逼近”,本质上是在做一种有方向的搜索,而不是盲目枚举。


为什么只移动较短的那一边?

这是整道题最关键的地方。

假设当前两根柱子分别是:

  • 左边高度 height[left]

  • 右边高度 height[right]

当前面积是:

(right - left) * min(height[left], height[right])

如果此时:

height[left] < height[right]

那么当前容器的高度就取决于左边这根较短的柱子。

换句话说,左边这根柱子就是当前的“短板”。

这时候如果你去移动右指针,会发生什么?

  • 宽度一定会变小

  • 而高度上限仍然受左边短板限制

也就是说,面积大概率不会变得更优。因为决定当前容量上限的是短板,不解决短板问题,单纯移动长板没有意义。

相反,如果移动左指针,就有机会找到一根更高的柱子。虽然宽度变小了,但如果新的短板更高,面积仍然有可能增大。

这就是为什么要移动较短的一边。

这个结论可以简单记成一句话:

想让面积变大,只能尝试提升短板。


代码拆解

下面结合代码,一步一步来看。


1. 初始化左右指针

int left = 0;
int right = height.length - 1;

左指针放在最左边,右指针放在最右边。

这样一开始拿到的是最大的宽度。


2. 计算当前面积

int currentArea = (right - left) * Math.min(height[left], height[right]);

这个公式很好理解:

  • (right - left) 表示宽度

  • Math.min(height[left], height[right]) 表示高度

因为水位只能到较短的那根柱子。


3. 更新最大值

maxArea = Math.max(maxArea, currentArea);

每算出一个面积,就和当前最大值比较,保留更大的那个。


4. 移动较短的一边

if (height[left] < height[right]) left++;
else right--;

如果左边更短,就移动左边;否则移动右边。

这里的 else 其实也包含了两边相等的情况。 当两边高度相等时,移动哪一边都可以,因为当前短板一样高,随便移动一边都不会漏掉最优解。


执行过程演示

我们用经典样例来手动模拟一下:

height = [1,8,6,2,5,4,8,3,7]

第一次

  • left = 0,高度 1

  • right = 8,高度 7

面积:

(8 - 0) * min(1, 7) = 8

当前最大面积是 8

因为左边更短,所以移动左指针。


第二次

  • left = 1,高度 8

  • right = 8,高度 7

面积:

(8 - 1) * min(8, 7) = 7 * 7 = 49

当前最大面积更新为 49

因为右边更短,所以移动右指针。


第三次

  • left = 1,高度 8

  • right = 7,高度 3

面积:

(7 - 1) * min(8, 3) = 6 * 3 = 18

49 小,不更新。

因为右边更短,继续移动右指针。


后面继续重复这个过程,最终最大值仍然是 49


为什么双指针不会漏掉答案?

很多人第一次学这道题的时候,最担心的问题就是:

“我每次只移动一边,会不会错过某个更优解?”

其实不会。

原因就在于:

当两端确定以后,面积由短板决定。 如果你不移动短板,而去移动长板,那么:

  • 宽度变小了

  • 短板没变,甚至可能更低

这种情况下,不可能得到比当前更优的结果。

所以移动长板是没有意义的,可以直接舍弃。

换句话说,双指针并不是“拍脑袋乱试”,而是在每一步都合理排除掉一部分不可能成为最优解的情况。

这其实就是一种典型的 贪心思想


复杂度分析

时间复杂度

O(n)

因为左右指针最多各移动 n 次,总共只会遍历数组一遍。

空间复杂度

O(1)

只用了几个额外变量,没有使用额外数组或集合。

这也是这道题双指针解法最大的优势:不仅快,而且非常省空间。


这道题真正想考什么?

表面上看,这是一道“求最大面积”的题。

但本质上,它想考的是你能不能从公式中提取出关键矛盾:

面积 = 宽度 × 短板高度

其中:

  • 宽度随着指针移动一定会变小

  • 所以如果想让面积有机会变大,就只能尽量提高短板高度

一旦你抓住这个核心,就能自然想到:

每次移动较短的那一边。

这类题目特别适合训练对“双指针 + 贪心”的理解。因为很多时候,最难的不是写代码,而是证明“为什么这么移动是对的”。


总结

这道题的代码其实很短,但思维含量很高。

它的核心可以概括成下面三点:

  1. 面积由宽度和短板共同决定

  2. 宽度在双指针收缩过程中一定会变小

  3. 所以想让面积变大,只能尝试移动短板,寻找更高的柱子

因此,最优解法就是:

  • 左右指针从两端出发

  • 每次计算当前面积

  • 更新最大值

  • 然后移动较短的一边

最终就能在 O(n) 的时间复杂度内解决问题。

如果你刚开始刷 LeetCode,这道题非常值得反复理解。 因为它不仅是一道高频面试题,也是一道非常典型的双指针启蒙题。

真正把这道题吃透之后,你对“什么时候该移动左指针,什么时候该移动右指针”会有更深的感觉。以后再遇到类似的数组对撞指针题,思路会顺很多。

如果对您有帮助的话,能否支持一下博主?
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇