「青训营 X 码上掘金」主题创作——主题四:攒青豆

84 阅读2分钟

当青训营遇上码上掘金

题目描述

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

解题思路

根据题意,我们可以将数组中的每个元素看成一个柱子,两个柱子间就可以组成一个盛雨水的容器。我们可以通过左右两个双指针来表示这个容器,初始化的容器则由最左边的柱子和最右边的柱子组成(最宽),然后再根据数组中的元素去细化容器的值。我们通过以下两种情况来具体分析。

每一列可以贡献的雨水 ,由其左右两侧最矮的那个墙高来决定

可以使用双指针(two pointer),分别从两侧开始遍历,并记录对于i点而言,左右两侧的最大墙高是多少 游标从左往右

leftH[i] = max(leftH[i-1], height[i-1]) //dp 游标从右往左

rightH[i] = max(rightH[i+1], height[i+1]) //dp 从左向右(or 右向左均可)

water[i] = min(leftH[i], rightH[i])-height[i]

从解法上看需要遍历三轮,那么时间复杂度为O(3n),已经可以视为是线性的了。可不可以再优化一下呢?

由于最后一次water[i]的遍历是必须的,那么无论它从左遍历,还是从右遍历,都可以得到leftH数组或rightH数组。

所以,可以将时间复杂度优化到O(2n),空间复杂度至少需要保存一个leftH或一个rightH,因此O(n)少不了。而另一边跟着water[i]一起的,因为只需要记录一个值,所以就不用再开数组了。

代码


func bean(height []int) int {
    n := len(height)
    if n < 3 {
        return 0
    }

    rightH := make([]int, n)
    rightH[n-1] = height[n-1]//右侧最高的墙设为跟自己等高
    for i := n-2; i >= 0; i-- {
        rightH[i] = max(rightH[i+1], height[i+1])
    }

    leftH := height[0]
    sum := 0
    for i := 1; i < n; i++ {
        water := min(leftH, rightH[i]) - height[i]
        //fmt.Println(i, height[i], leftH, rightH[i], water)
        if water > 0 {
            sum += water
        }
        leftH = max(leftH, height[i])
    }
    return sum
}

func max(a,b int) int {
    if a > b {
        return a
    }else{
        return b
    }
}

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