当青训营遇上码上掘金-攒青豆

71 阅读2分钟

当青训营遇上码上掘金

有一个攒青豆的问题,题面如下:

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

这个问题其实就是接雨水,我们先讲思路,然后给出代码

定性地看一下,一个地方能攒青豆的多少,其实是取决于它左右两边的“墙”有多高。定量地说,这个值等于左边最高的墙和右边最高的墙的最小值,再减去这个地方本身的高度,当然,这个值必须是非负的才合法。

所以我们要模拟这个思路就很简单——从每个“盆底”都出发找左右的最高“墙”,每一个地方都算一个数,最后全部加到一起。这个方法肯定可以出结果,但是在找“墙”的时候有大量的重复计算。这个重复非常简单,朴素地想,临近的几个“盆底”很可能找到的“墙”是同一个,但是我找了好几次,这肯定有重复。

我们就想,怎么去除这个重复,一次找到的“墙”能直接适用于很多个“盆底”。其实很简单,我们逆向思维,之前是有“盆底”去找“墙”,现在我就控制“墙”,往里面找“盆底”。

我们一步步往里面看“盆底”,看它是否符合现在的“墙”,符合的话当然好,就可以算这个地能装多少。如果不能符合,那它就不能装,而足够高,可以看成是一个“墙”。现在的问题就是这个看“盆底”的顺序是什么,看左边的还是看右边的。其实也简单,结论是看两边的“墙”谁更高,让矮的那边往里看“盆底”。这是因为如果高的那边往里看,可能会忽视掉几个高的数字形成的凹槽。而矮的往里看,它就是这样的结论。

所以我们就得到了代码

var 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 (leftMax<rightMax ) {
            ans += leftMax - height[left];
            ++left;
        } else {
            ans += rightMax - height[right];
            --right;
        }
    }
    return ans;
};