[前端]_一起刷leetcode 11. 盛最多水的容器

108 阅读3分钟

大家好,我是挨打的阿木木,爱好算法的前端摸鱼老。最近会频繁给大家分享我刷算法题过程中的思路和心得。如果你也是想提高逼格的摸鱼老,欢迎关注我,一起学习。

题目

11. 盛最多水的容器

给你 n 个非负整数 a1,a2,...,a``n,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 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

示例 3:

输入: height = [4,3,2,1,4]
输出: 16

示例 4:

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

 

提示:

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

思路

  1. 首先我们要搞明白什么是最大面积,就是左右两条边取最短的那条边,然后*当前左右两条边的距离;
  2. 那么最简单的暴力做法是,直接遍历每一种可能形成的组合,暴力枚举一边所有的值,然后把最大值记录下来返回即可。

实现

/**
 * @param {number[]} height
 * @return {number}
 */
var maxArea = function(height) {
    const n = height.length;

    let maxValue = 0;
    for (let i = 0; i < n; i++) {
        for (j = i + 1; j < n; j++) {
            // 面积等于最短的边 * 距离
            let area = Math.min(height[i], height[j]) * (j - i);
            maxValue = Math.max(maxValue, area);
        }
    }

    return maxValue;
};

翻车

image.png

直接原地超时,显然这道题目没有这么简单,想想办法优化一下。

我想了一下,可以先通过两轮遍历,找到所有的有效边。

第一轮从左往右遍历,找到所有有效的左边界,如果右边的值比左边的还小说明是无效值不用考虑; 第二轮从右往左遍历一样找有效右边界。

然后枚举有效边界的所有可能性即可。

优化代码

/**
 * @param {number[]} height
 * @return {number}
 */
var maxArea = function(height) {
    const n = height.length;

    let left = 0, right = n - 1;

    let leftList = [ left ], 
        rightList = [ right ];

    let prev = height[left];

    // 从左到右扫描一遍
    for (let i = 0; i < n; i++) {
        if (height[i] > prev) {
           leftList.push(i);
           prev = height[i];
        }
    }

    // 从右往左扫描一遍
    prev = height[right];
    for (let i = n - 1; i > 0; i--) {
        if (height[i] > prev) {
           rightList.push(i);
           prev = height[i];
        }
    }

    let maxValue = 0;

    // 枚举所有有效值来比较
    for (let i = 0; i < leftList.length; i++) {
        for (let j = 0; j < rightList.length; j++) {
            let ln = leftList[i], rn = rightList[j];
            // 如果右边比左边索引小就不用往后走了
            if (rn > ln) {
                const curArea = Math.min(height[ln], height[rn])  * (rn - ln);
                maxValue = Math.max(maxValue, curArea);
            } else {
                break;
            }
        }
    }

    return maxValue;
};

再次优化 双指针

上述代码我们虽然找到了思路,但是整体的性能并不高,我们没必要通过左右两个数组来存储所有有效的边界,只需要用贪心算法尽可能保证面积的大。我们可以用两个指针来表示边界所在的位置,每次移动最小的边界,让其往里走一格,枚举所有结果直到两者相遇。

最终代码

/**
 * @param {number[]} height
 * @return {number}
 */
var maxArea = function(height) {
    let left = 0, right = height.length - 1;
    let maxValue = 0;

    while (left < right) {
        const minHeight = Math.min(height[left], height[right]);
        maxValue = Math.max(maxValue,  minHeight  * (right - left));
        // 最短的边往里走一个格子
        if (minHeight === height[left]) {
            left++;
        } else {
            right--;
        }
    }

    return maxValue;
};

看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。