LeetCode 热题 100 之第5题 盛最多水的容器(JavaScript篇)

187 阅读4分钟

传送门:11. 盛最多水的容器 - 力扣(LeetCode)

题目

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。

说明: 你不能倾斜容器。

示例 1:

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

示例 2:

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

提示:

  • n == height.length
  • 2 <= n <= 105
  • 0 <= height[i] <= 104

解题思路

可以采用双指针贪心策略求解“盛最多水的容器”问题,具体思路如下:

  1. 初始化双指针:左指针 l 从数组起始位置(0)开始,右指针 r 从数组末尾位置(height.length - 1)开始,同时初始化最大面积 mul 为 0。

  2. 计算当前容器面积:在循环中,通过公式 (r - l) * Math.min(height[l], height[r]) 计算当前指针所指两块板形成的容器面积,其中 r - l 是宽度,Math.min(height[l], height[r]) 是短板高度(决定容器高度)。

  3. 更新最大面积:使用 Math.max(mul, area) 将当前面积与历史最大面积比较,保留较大值。

  4. 移动指针的贪心策略:若左板高度小于右板(height[l] < height[r]),右移左指针 l++;否则左移右指针 r--。这是因为移动短板可能找到更高的板,从而增加面积,而移动长板只会减小宽度且无法增加高度。

  5. 循环终止条件:当左指针 l 大于等于右指针 r 时,循环结束,返回最大面积 mul

该算法通过单次遍历(时间复杂度 O(n))和常数空间(空间复杂度 O(1))高效求解,体现了贪心算法“局部最优推导全局最优”的思想。

代码

/**
 * @param {number[]} height
 * @return {number}
 */
var maxArea = function(height) {
   // 初始化变量:
   // mul: 存储最大面积(初始值为0)
   // l: 左指针(从数组起始位置开始)
   // r: 右指针(从数组末尾位置开始)
   let mul = 0, l = 0, r = height.length - 1;
   // 双指针循环条件:左指针小于右指针
   while(l < r) {
    // 计算当前容器面积:宽度 × 短板高度
    // 使用const是因为每次循环的area都是全新变量,且只赋值一次
    const area = (r - l) * Math.min(height[l], height[r]);
    // 更新最大面积(取当前面积和历史最大面积的较大值)
    mul = Math.max(mul, area);
    // 贪心策略:移动较短的板以寻找更大面积
    if(height[l] < height[r]) {
        l++; // 左板较短,右移左指针
    } else {
        r--; // 右板较短或相等,左移右指针
    }
   }
   // 返回最大面积
   return mul;
};

每天一学(小白视角:为什么 area 每次变化还能用 const?)

1. const 的真正含义:「当前作用域内不可重新赋值」

const area = ... 中的 const 限制的是 「在当前作用域内不能给 area 重新赋值」,而不是「值永远不变」。

  • 关键:area 是「每次循环创建的新变量」 在代码中,area 是在循环内部声明的(比如 for 循环或 while 循环)。每次循环都会创建一个 全新的 area 变量,它们是不同的内存空间,互不干扰。
    // 伪代码演示循环中的作用域
    while (l < r) {
      // 第一次循环:创建新变量 area = 计算值1(const声明,当前作用域不可改)
      const area = (r-l)*Math.min(height[l], height[r]);
      
      // 第二次循环:创建新变量 area = 计算值2(又是一个全新的const变量)
      const area = (r-l)*Math.min(height[l], height[r]);
    }
    
    就像你每次考试都会用一张「新的答题卡」,每张答题卡(area)只写一次答案,写完就收走,互不影响。

2. 为什么 constlet 更好?

  • const 明确表达「一次性赋值」的意图 area 的值在每次循环中只计算一次,之后不会再修改(比如不会出现 area = area + 1 这样的操作)。用 const 告诉阅读者:「这个变量在当前循环里就是个固定值,放心用,不会变」。
  • let 可能隐藏错误风险 如果用 let area = ...,虽然语法没错,但可能让人误以为「这个 area 后面会被修改」。万一不小心写了 area = 0 这样的代码,const 会直接报错提醒你,而 let 会默默执行,导致难以排查的 bug。

3. 一句话总结

area 虽然「每次循环的值不同」,但「每次都是全新的变量」,且「每个变量只赋值一次」。const 刚好匹配这种场景,既安全又清晰,而 let 反而会模糊代码意图。

(小白友好比喻:const 像一次性水杯,每次用新的,用完即弃;let 像可重复使用的杯子,虽然能用,但这里没必要,还可能被别人误用装别的东西。)