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

60 阅读2分钟
  • 主题 4:攒青豆

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

攒青豆.png

以下为上图例子的解析:

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

做题历程

  • 一看到这个题目我就感到很喜感,挺有意思的,还非常应景,所以我就选择了这个主题——攒青豆。
  • 看到题目,觉得很熟悉似乎做过,一时还想不起来哪里做过
  • 一看图片,一切豁然开朗, 这不是接雨水吗这一下子就有了思路,拿捏!

算法思路

就是计算从每一个下标对应左右两边的最大高度;
然后用最大高度减去该下标对应的高度即可求出答案。

方法一 简单遍历

每一个柱子能够接到的青豆数取决于其左边最高的柱子与其右边最高的柱子中比较矮的那一个的高度减去其自身的高度。

这样的话,可以先创建两个数组,left_height[] 和 right_height[] 分别存储当前柱子左边的最高柱子高度和当前柱子右边的最高柱子高度。

然后取出当前位置 i 的 left_height[ i ] 和 right_height[ i ] 中比较矮的那一个,再减去当前位置的柱子本身的高度。

最后再相加就是总的高度,从而求出多少单位的青豆数。

func trap(height []int) int {
    left_height := []int{}
    left_max := 0
    for _,i := range height{
        if i > left_max{
            left_max = i
            left_height = append(left_height,i)
        }else{
            left_height = append(left_height,left_max)
        }
    }
    fmt.Println(left_height)

    right_height := make([]int,len(height))
    right_max := 0

    for i := len(height)-1; i>-1; i--{
        if height[i] > right_max{
            right_max = height[i]
            right_height[i] = height[i]
        }else{
            right_height[i] = right_max
        }
    }
    fmt.Println(right_height)

    ras := 0
    for i := 0; i<len(height); i++{
        ras += min(left_height[i],right_height[i])-height[i]
    }

    return ras

}

func min(a,b int) int{
    if a < b{
        return a
    }else{
        return b
    }
}

方法二:单调栈

除了计算并存储每个位置两边的最大高度以外,也可以用单调栈计算能接的雨水总量。

维护一个单调栈,单调栈存储的是下标,满足从栈底到栈顶的下标对应的数组

func trap(height []int) (ans int) {
    stack := []int{}
    for i, h := range height {
        for len(stack) > 0 && h > height[stack[len(stack)-1]] {
            top := stack[len(stack)-1]
            stack = stack[:len(stack)-1]
            if len(stack) == 0 {
                break
            }
            left := stack[len(stack)-1]
            curWidth := i - left - 1
            curHeight := min(height[left], h) - height[top]
            ans += curWidth * curHeight
        }
        stack = append(stack, i)
    }
    return
}
func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}

以下是使用双指针的改进方法。

方法三:双指针

通过观察,我们发现:

两边高就使中间形成凹槽, 青豆高度取决于两边最高的柱子中比较低的那一个 从两边开始搜索,哪边比较低,就把哪边的指针向前移动,进行搜索,另一个不动的指针指向的是当前最高的柱子。

同时,设置 max 记录当前位置i的高度,也就是正在搜索的这一边中,搜索过的最高值。

每搜索一个位置,总的青豆数量都增加 max - height[当前位置]。

func trap(height []int) int {
   
    i := 0
    j := len(height)-1

    ras := 0
    max := 0

    //直到两指针相遇
    for i < j {
        //i这边比较矮,就将i向前移动
        if height[i] <= height[j]{
            //特殊判断,i处于起始位置
            if i == 0 {  
                max = height[i]
                i++
                continue
            }
            //特殊判断,遇到更高的柱子
            if height[i] > max{
                max = height[i]
            }
            ras +=  max - height[i]
            i++
        }else{  //j这边比较矮,就将j向前移动
            //特殊判断,j处于起始位置
            if j == len(height)-1 {
                max = height[j]
                 j--
                continue
            }
            //特殊判断,遇到更高的柱子
            if height[j] > max{
                max = height[j]
            }
            ras += max - height[j]
            j--
        }
    }
    
   return ras

}