「青训营 X 码上掘金」—— 攒青豆

50 阅读2分钟

当青训营遇上码上掘金

1. 主题介绍

现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)

image.png

以下为上图例子的解析:

输入:height = [5,0,2,1,4,0,1,0,3]  
输出:17  
解析:上面是由数组 [5,0,2,1,4,0,1,0,3] 表示的柱子高度,在这种情况下,可以接 17 个单位的青豆。

这道题的原型实际上是力扣经典接雨水问题,也是面试常考题。

2. 解法思路

2.1 按列求

求每一列的水,我们只需要关注当前列,以及左边最高的墙,右边最高的墙就够了。装水的多少取决于左右两边较矮的墙。

代码如下:

public int trap1(int[] height) {
    int sum = 0;
    for (int i = 1; i < height.length - 1; i++) {
        int max_left = 0;
        for (int j = i - 1; j >= 0; j--) {
            if (height[j] > max_left) {
                max_left = height[j];
            }
        }
        int max_right = 0;
        for (int j = i + 1; j < height.length; j++) {
            if (height[j] > max_right) {
                max_right = height[j];
            }
        }
        int min = Math.min(max_left, max_right);
        if (min > height[i]) {
            sum += (min - height[i]);
        }
    }
    return sum;
}

时间复杂度:O(n²)。

空间复杂度:O(1)。

2.2 动态规划

在2.1的解法中,对于每一列求其左右最高墙时,都是重新遍历一遍所有高度,这里可以提前计算每一列左右最高墙的高度并保存,以进行优化。

使用max_left [i] 代表第 i 列左边最高的墙的高度,max_right[i] 代表第 i 列右边最高的墙的高度。

max_left [i] = Max(max_left [i-1],height[i-1])

max_right[i] = Max(max_right[i+1],height[i+1])

完整代码如下:

public int trap2(int[] height) {
    int sum = 0;
    int[] max_left = new int[height.length];
    int[] max_right = new int[height.length];
    
    for (int i = 1; i < height.length - 1; i++)
        max_left[i] = Math.max(max_left[i - 1], height[i - 1]);
    for (int i = height.length - 2; i >= 0; i--)
        max_right[i] = Math.max(max_right[i + 1], height[i + 1]);
 
    for (int i = 1; i < height.length - 1; i++) {
        int min = Math.min(max_left[i], max_right[i]);
        if (min > height[i]) sum = sum + (min - height[i]);
    }
    return sum;
}

时间复杂度:O(n)。

空间复杂度:O(n)。

2.3 双指针

max_left [ i ] 和 max_right [ i ] 数组中的元素我们其实只用一次,然后就再也不会用到了。

public int trap3(int[] height) {
    int ans = 0;
    int left = 0, right = height.length - 1;
    int left_max = 0, right_max = 0;
    while (left < right) {
        left_max = Math.max(left_max, height[left]);
        right_max = Math.max(right_max, height[right]);
        if (height[left] < height[right]) ans += left_max - height[left++];
        else ans += right_max - height[right--];
    }
    return ans;
}

时间复杂度: O(n)。

空间复杂度: O(1)。

3. 码上掘金

我的码上掘金不知道怎么调成后端语言,试了所有的按钮都不行,后面复制了一个别人的后端仓库,然后改成自己的代码。(如果你们不知道怎么调成后端语言也可以复制我的,知道的话可以在评论区评论一下)

参考

详细通俗的思路分析,多解法 - 接雨水 - 力扣(LeetCode)

「青训营 X 码上掘金」主题 4——攒青豆 - 掘金 (juejin.cn)