LeetCode热题(JS版)- 42. 接雨水(三种解法)

306 阅读2分钟

题目

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例 1:

image.png

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。

示例 2:

输入:height = [4,2,0,3,2,5]
输出:9

提示:

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

思路1:正反扫描

可以使用动态规划来预处理左右两侧的最大高度,使得每次查询的时间复杂度为 O(1)。 具体来说,我们可以分别使用两个数组 leftMax 和 rightMax 来记录每个位置左侧和右侧的最大高度。然后我们可以遍历一遍数组,分别计算每个位置的积水量,最后将所有的积水量加起来就是答案。

function trap(height: number[]): number {
    const len = height.length;
    if(!len) return 0;
    const maxL = [];
    const maxR = [];
    
    // 两端初始值
    maxL[0] = height[0];
    maxR[len - 1] = height[len - 1];
    
    // 扫描左边最大值
    for(let i = 1; i < len; i++) {
        maxL[i] = Math.max(maxL[i - 1], height[i])
    }

    // 扫描右边最大值
    for(let i = len - 1 - 1; i >= 0; i--) {
        maxR[i] = Math.max(height[i], maxR[i + 1])
    }

    // 取左右最小值 - 当前高度
    let res = 0;
    for(let i = 0; i < len; i++) {
        res += Math.min(maxL[i], maxR[i]) - height[i];
    }

    return res;
};

image.png

  • 时间复杂度为 O(n)
  • 空间复杂度为 O(n)。

图解参考:michael.blog.csdn.net/article/det…

思路2:双指针

这道题目可以使用双指针的思想来解决。具体来说,我们可以维护两个指针 left 和 right,分别从两端开始向中间遍历。在遍历的过程中,我们需要维护两个变量 leftMax 和 rightMax,分别表示 left 左侧和 right 右侧的最大高度。如果 height[l] < height[r],说明此时 left 可以积水,积水的高度为 leftMax - height[left]。反之,说明此时 right 可以积水,积水的高度为 rightMax - height[right]。遍历完成后,所有的积水量之和就是答案。

function trap(height: number[]): number {
    const len = height.length;
    if(!len) return 0;

    // 双指针法
    let l = 0, r = len - 1;
    let maxL = 0, maxR = 0, res = 0;
    while(l < r) {
        if(height[l] < height[r]) {
            // 右侧有高墙
            height[l] >= maxL ? (maxL = height[l]) : res += maxL - height[l];
            l++
        } else {
            // 左侧有高墙
            height[r] >= maxR ? (maxR = height[r]) : res += maxR - height[r];
            r--
        }
    }

    return res;
};

image.png

  • 时间复杂度 这道题目的时间复杂度为 O(n),其中 n 是数组的长度。需要遍历数组中的每个元素一次,并且每个元素最多被访问两次,因此时间复杂度为 O(n)。
  • 这道题目是一个非常经典的题目,需要使用双指针的思想来解决。在实现过程中需要注意边界条件的处理,以及对左右两侧最大高度的维护。

思路3:递减栈

我们可以使用栈来维护当前的递减序列。具体来说,我们遍历每个位置,如果当前位置的高度小于等于栈顶元素的高度,说明这个位置不能形成凹槽,直接将这个位置入栈;否则,我们需要计算当前位置能够形成的凹槽的面积,并更新答案。

function trap(height: number[]): number {
    const len = height.length;
    let res = 0;
    if(!len) return 0;

    // 单调递减栈
    const stack = [];
    for(let i = 0; i < len; i++) {
        // 栈存在,且当前元素大于栈顶元素
        while(stack.length && height[i] > height[stack[stack.length - 1]]) {
            const top = stack.pop();
            if(!stack.length) break;
            const left = stack[stack.length - 1];
            const width = i - left - 1;// 中间同样高度的几个
            console.log(width)
            const diff = Math.min(height[left], height[i]) - height[top];// 两边最小水高 - 容器高
            res += width * diff;
        }

        stack.push(i);// 向下的斜坡\,会一直递减。一旦不递减,就进入出栈的流程。
    }
    return res;
};

image.png

  • 时间复杂度为 O(n)
  • 空间复杂度为 O(n)