算法-接雨水(三种解法)|刷题打卡

471 阅读2分钟

1 算法简介

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

示例 1:

输入: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

算法简介来自力扣(LeetCode)42

2 思路

  1. 思考一个位置接的雨水多少与哪些因素有关?如何计算?
  2. 如何计算相关因素?

这里暂用下标 i 表示具体的位置,用 num[i] 表示位置 i 接的雨水多少。

对于问题 1:

根据图片示意可知,num[i] 大小由它左边的最高值(left_max)与右边的最高值(right_max)中的较小值决定(与桶的容量由最短板决定类似)。

因此 num[i] = Math.min(left_max, right_max) - height[i](备注:height[i] 是位置 i 本身的高度,需要减去)。

对于问题 2:

在计算 left_maxright_max 时,不同的方法方式略有不同,直接想到的就是以 i 为界逐个比较找到最大值,具体做法将在下面的代码部分详细阐述。

3 解法

这里使用 JavaScript 书写相关解法。

3.1 暴力解法

遍历所有接雨水的位置,分别计算每个位置能接多少雨水,再求和返回即可。

var trap = function (height) {
    let res = 0, n = height.length;
    if (n === 0) return 0;
    // 第一个位置和最后一个位置都接不到雨水
    // 所以 i 的范围是 (0, n-1)
    for (let i = 1; i < n - 1; i++) {
        let left_max = 0, right_max = 0;
        // 但计算 left_max 和 right_max 时,需要考虑首尾位置的高度
        for (let j = i; j >= 0; j--) left_max = Math.max(height[j], left_max)
        for (let j = i; j < n; j++) right_max = Math.max(height[j], right_max);
        // 将每个位置能接的雨水加在一起
        res += Math.min(left_max, right_max) - height[i];
    }
    return res;
};

时间复杂度:O(N^2)

空间复杂度:O(1)

3.2 优化

暴力解法中,将求 left_maxright_max 的计算都写在了循环里,比较耗时,其实可以先分别计算每个位置的 left_maxright_max,再遍历数组求出每个位置接的雨水多少。

var trap = function (height) {
    let res = 0, n = height.length;
    if (n === 0) return 0;

    let left_max = new Array(n);
    let right_max = new Array(n);
    left_max[0] = height[0];
    right_max[n - 1] = height[n - 1];
    
    // 注意这里计算最大值的不同之处在于
    // 位置 i 的左边最大值是前一个位置的左边最大值与 height[i] 比较
    // 右边最大值同理
    for (let i = 1; i < n - 1; i++) left_max[i] = Math.max(left_max[i - 1], height[i]);
    for (let i = n - 2; i >= 0; i--) right_max[i] = Math.max(right_max[i + 1], height[i]);
    // 每个位置接的雨水求和
    for (let i = 1; i < n - 1; i++) res += Math.min(left_max[i], right_max[i]) - height[i];
    
    return res;
};

时间复杂度:O(N),虽然是遍历了三次,但是分开的,不是嵌套的。

空间复杂度:O(N),使用了两个数组。

3.3 双指针

巧妙解法双指针,优化空间复杂度。

var trap = function (height) {
    let res = 0, n = height.length;
    if (n === 0) return 0;

    // 两个指针
    let left = 0, right = n - 1;

    // 左右最大值
    let left_max = height[0];
    let right_max = height[n - 1];

    while (left <= right) {
        left_max = Math.max(left_max, height[left]);
        right_max = Math.max(right_max, height[right]);
        if (left_max < right_max) {
            res += left_max - height[left];
            left++;
        } else {
            res += right_max - height[right];
            right--;
        }
    }

    return res;
};

时间复杂度:O(N)

空间复杂度:O(1)

4 总结

这道题比较经典,直播中字节面试官说是字节面试中出现频率很高的题,同时双指针是比较经典的解法,后面有空再总结同系列的题,谢谢大家。

第一次写技术类文章,如有不当之处,欢迎指出哟(^U^)ノ~YO

本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情