一名小白对攒青豆的理解

59 阅读3分钟

当青训营遇上码上掘金

回顾一下题目要求

  • 攒青豆

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

image.png

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

话不多说,先上题解

const trap = function(height){
    let ans = 0;
    let left = 0, right = height.length - 1;
    let leftMax = 0, rightMax = 0;
    while (left <= right){
        leftMax = Math.max(leftMax, height[left]);
        rightMax = Math.max(rightMax, height[right]);
        if (height[left] < height[right]) {
            ans += leftMax - height[left];
            ++left;
        } else {
            ans += rightMax - height[right];
            --right;
        }
    }
    return ans;
};

看起来一通让人似懂非懂的操作,就算出了正确的青豆数

这段代码由于太过对称,所以小白我就当即背下了题解

但是为了感受到这段代码的妙,咱们还是从头推演一遍吧


let ans = 0;
let left = 0, right = height.length - 1

首先这段没什么好说的,准备左指针和右指针,那必然是向中遍历了


let leftMax = 0, rightMax = 0; 
while (left < right) {
    leftMax = Math.max(leftMax, height[left]); 
    rightMax = Math.max(rightMax, height[right]);

....
}

这里用上了两个变量,我们尝试理解这两个变量的意义

leftMax = Math.max(leftMax, height[left]);

首先height[left]即为当前左指针遍历到的柱子高度

这一行代码里我们不断更新 left 指针左侧的最高柱子leftMax

同理, rightMax即为 right 指针右侧的最高柱子



while (left < right) { 
    ......
    if (height[left] < height[right]){
        ans += leftMax - height[left];
        ++left;
    } else {
        ans += rightMax - height[right];
        --right; 
    }
}

重点来了,我们为什么需要最高的柱子呢?当然是为了计算青豆的体积,重点在于,我们只计算当前指针这一列的青豆体积,它只与当前列的高度,与左右两侧最大柱子高度有关

比如说

            0
0           0
0   0   0   0
1   2   3   4

二号柱上能放多少体积青豆呢?

显然是1体积

            0
0   X       0
0   0   0   0
1   2   3   4

已知leftMax为一号柱的高度2

那么二号柱的青豆可以表示为 一号柱的高度减去二号柱的高度 leftMax - height[left]


那么问题来了,如果右边柱子高度不够,该怎么办呢?

            
0   X       
0   0   0   0
1   2   3   4

我们通过这个if判断来保证

if (height[left] <=height[right]){ 
    ...
}

右指针保证了右边存在高于当前柱子高度的柱子


那么问题来了,这也是右边存在高于二号柱的情况,

可是这一眼看上去就不合法了啊?

0   X      
0   X       0
0   0   0   0
1   2   3   4


这种情况并不会发生,因为我们永远只从安全的一侧出发

0        
0           0
0   0   0   0
1   2   3   4

一开始我们发现左指针一号柱高于右指针四号柱

所以在三号柱堆豆子时,由于右侧柱的高度一定小于左侧的某一根柱子,

所以不管怎样,我们用rightMax - height[right]算出的高度一定不会超过左侧柱子高度,

这就是从右往左到达三号柱的理由

即用这个else条件来保证

if (height[left] <=height[right]){ 
... 
}else{
...
    --right
}

如果我们发现三号柱比一号柱还高,那我们大可从左边开始,因为左边才是安全的一侧

最终当L=R时,我们退出循环,此时所有列的豆子都已经被计算好了,得到了ans


总之通过if判断,

我们既可以保证当前方向有足够的高度,又能保证左指针和右指针走到哪,哪就是安全的一边

两个判断合二为一,又能完整地遍历一遍数组,妙不可言


算法过于神奇,静下心来理解,慢慢积累!