码上掘金 | 青训营笔记

107 阅读4分钟

当青训营遇上码上掘金之“攒青豆”

题目:攒青豆

现有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 个单位的青豆。

解题思路

黑色的看成墙,蓝色的看成豆子,宽度一样,给定一个数组,每个数代表从左到右墙的高度,求出能装多少单位的豆子。也就是图中蓝色正方形的个数。

解法一:按行求

这是我最开始想到的一个解法,就是先求高度为1的豆子,再求高度为2的豆子,再求高度为3的豆子。

整个思路就是,求第i层的豆子,遍历每个位置,如果当前的高度小于i,并且两边有高度大于等于i的,说明这个地方一定有豆子,豆子就可以加1

如果求高度为i的豆子,首先用一个变量temp保存当前累积的豆子,初始化为0。从左到右遍历墙的高度,遇到高度大于等于i的时候,开始更新temp。更新原则是遇到高度小于i的就把temp加1,遇到高度大于等于i的,就把temp加到最终的答案ans里,并且temp置零,然后继续循环。

代码实现:

public int trap(int[] height) {
    int sum = 0;
    int max = getMax(height);//找到最大的高度,以便遍历。
    for (int i = 1; i <= max; i++) {
        boolean isStart = false; //标记是否开始更新 temp
        int temp_sum = 0;
        for (int j = 0; j < height.length; j++) {
            if (isStart && height[j] < i) {
                temp_sum++;
            }
            if (height[j] >= i) {
                sum = sum + temp_sum;
                temp_sum = 0;
                isStart = true;
            }
        }
    }
    return sum;
}
private int getMax(int[] height) {
    int max = 0;
    for (int i = 0; i < height.length; i++) {
        if (height[i] > max) {
            max = height[i];
        }
    }
    return max;
}

解法二:按列求

求每一列的豆子,我们只需要关注当前列,以及左边最高的墙,右边最高的墙就够了。

装豆子的多少,当然根据木桶效应,我们只需要看左边最高的墙和右边最高的墙中较矮的一个就够了。

所以,根据较矮的那个墙和当前列的墙的高度可以分为三种情况。

  • 较矮的墙的高度大于当前列的墙的高度

image.png

把正在求的列左边最高的墙和右边最高的墙确定后,然后为了方便理解,我们把无关的墙去掉。

image.png

这样就很清楚了,现在想象一下,往两边最高的墙之间撒豆子。正在求的列会有多少豆子?

很明显,较矮的一边,也就是左边的墙的高度,减去当前列的高度就可以了,也就是 2 - 1 = 1,可以存一个单位的豆子。

  • 较矮的墙的高度小于当前列的墙的高度

image.png

同样的,我们把其他无关的列去掉。

image.png

想象下,往两边最高的墙之间撒豆子。正在求的列会有多少豆子?

正在求的列不会有豆子,因为它大于了两边较矮的墙。

  • 较矮的墙的高度等于当前列的墙的高度。

和上一种情况是一样的,不会有豆子。

image.png

明白了这三种情况,程序就很好写了,遍历每一列,然后分别求出这一列两边最高的墙。找出较矮的一端,和当前列的高度比较,结果就是上边的三种情况。

代码实现:

public int trap(int[] height) {
    int sum = 0;
    //最两端的列不用考虑,因为一定不会有豆子。所以下标从 1 到 length - 2
    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 = sum + (min - height[i]);
        }
    }
    return sum;
} 

结果展示

测试代码:

public static void main(String []args) {
    int[] height = {5,0,2,1,4,0,1,0,3};
    int res=trap(height);
    System.out.print("最后接住的青豆是:");
    System.out.print(res);
}

输出结果:

image.png

总结

完整代码链接:青训营4-攒青豆 - 码上掘金 (juejin.cn)

笨人纯小白!!如果有其他的思路或者更好的解法,亦或者发现了文章出现了错误或有不足,欢迎在评论区批评指正!