当青训营遇上码上掘金|主题4:攒青豆

80 阅读4分钟

题目描述

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

725ef710a59944d49d0315bece7a1ac1_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png

以下为上图例子的解析:

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

解题过程

首先让我们看看题目是如何得出17这个答案的,见下图: 绿色部分即为青豆:

屏幕截图 2023-01-14 203928.png 以行的视角来看即:(1 + 2) + (2 + 3) + (3 + 3) + (3) = 17

由此可得到第一种解法,即求出每一行的青豆数目然后进行求总和即可:

解法一 :按行求个数,最后每行个数相加

思路:我们很自然的想到,青豆可以被计数,是因为这么一个状态,其两边高度均大于它的高度即“凹陷”。

每行的青豆高度都是不同的,由于我们要遍历,规定其高度为i,那么我们何时确定要开始计入青豆了呢?

上面我们提到青豆的“凹陷”,即当该列的高度大于i时我们要开始计数,我们可以用count来计数,那么何时计数终止呢?抓住“凹陷”这个状态!它是不是两边高中间低?没错我们计数的结束就在于第二次遇到某列的高度大于i!计数结束了,就应当清空count,同时我们用一个sum来记录总青豆数。

屏幕截图 2023-01-14 203928.png 下面是Java代码

public int countGreen(int[] height) {
    int countColumn = height.length;//列数
    int countRow = getRows(height);//行数
    int sum = 0;//总的青豆数目
    //对每一行求解
    for (int i = 1; i <= countRow; i++) {//i表示了青豆的高度,用1开始
        int temp = 0;//每个凹陷中的青豆数目
        //我们用一个flag来标记每次凹陷的开始,默认是false未开始
        boolean flag = false;
        for (int j = 0; j < countColumn; j++) {
            if(height[j] < i && flag){
                temp++;
            }
            if(height[j] >= i){
                //还记得上面的分析吗,抓住“凹陷”这个特征,此时是其左边。新的一轮开始了!
                flag = true;//标记开始
                sum = sum + temp;//将上一个凹陷中的个数加上
                temp = 0;//新的开始,置0
            }

        }
    }
    return sum;
}

private int getRows(int[] height) {
    int max = Integer.MIN_VALUE;
    for (int i : height) {
        if(i > max) max = i;
    }
    return max;
}

算法复杂度分析:时间复杂度o(m*n) 空间复杂度o(1)

解法二 :按列求个数,最后每列个数相加

若m,n数据过大那么运行效率必然很低,此时我们再回到这张图来分析一下:

屏幕截图 2023-01-14 220033.png

以行的视角来看是:(1 + 2) + (2 + 3) + (3 + 3) + (3) = 17

那我们换个视角来看: (0) + (4-0) + (4-2) + (4-1) + (0) + (3-0) + (3-1) + (3-0) + (0) = 17

这是什么视角呢?没错是以列的视角来看待的。

让我们来分析一下数据:

第一列:0,很自然可以得到 第二列:(4 - 0)即肉眼可见的4,那么4 - 0中的4是?0是?其实不难发现4即是第5列的高度,而0就是当前的高度,那么请问为什么左边的5明明比右边的4高而不用5呢?这个道理很简单因为当前列的右边的最高高度为4,所以不能承受高度5即每一列的个数其实就是下面这个道理:

假定当前高度为i,则青豆数目为其左边和右边最大高度中的较小值,然后该值减去当前列的高度就是数目。

但是我们看看第5列它自身高度为4,左边有最大高度5,右边最大高度3,我们总不能用3 - 4吧,没错这是一种特殊情况,即当该列的高度大于两侧最大高度的较小值时我们可以肯定的说青豆数目为0!

请注意此处每一列不参与左右两侧高度的比对!

以下是Java代码:

private int countGreen(int[] height) {
    int countColumn = height.length;//列数
    int sum = 0;//总的青豆数目
    int[] leftmax = new int[countColumn];
    int[] rightmax = new int[countColumn];
    //先求出每一列(除去自身)的左右侧的最大高度
    for (int i = 1; i < countColumn; i++) {
        //第一列即i = 0时,很自然得出left[0]=0,所以直接从1开始
        leftmax[i] = Math.max(leftmax[i - 1],height[i - 1]);
    }
    for (int i = countColumn - 2; i >= 0; i--) {
        //最后一列列即i = countColumn - 1时,很自然得出right[countColumn - 1]=0,所以直接从countColumn - 2开始
        rightmax[i] = Math.max(rightmax[i + 1],height[i + 1]);
    }
    //得出每一列其左右两侧最大高度后,开始遍历每一列
    for (int i = 0; i < countColumn; i++) {
        int tmp = Math.min(leftmax[i],rightmax[i]);//取出该列左右两侧最高高度的较小值
        if(tmp < height[i])  continue;//直接跳过即加0
        sum += tmp - height[i];
    }
    return sum;
}

算法复杂度分析:时间复杂度o(n) 空间复杂度o(n)

总结:结束了