当青训营遇上码上掘金之——“攒青豆”

82 阅读2分钟

当青训营遇上码上掘金

所选主题:攒青豆

题目描述

现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)。 725ef710a59944d49d0315bece7a1ac1~tplv-k3u1fbpfcp-zoom-in-crop-mark-3024-0-0-0.image.png 上面是由数组 [5,0,2,1,4,0,1,0,3] 表示的柱子高度,在这种情况下,可以接17个单位的青豆。

思路分析

这题要求的是最多能承载的青豆数量,青豆承载在由柱子所组成的容器内,而容器的容量只取决于容器中最短的那根柱子,我们不难发现这类似于“木桶原理”,因此本题的关键点在于如何寻找承载青豆的容器并找到容器的最短边。

方法一:暴力法

这个方法很容易想到,我们可以以列为单位从左往右进行遍历,找到每一列所能承载的青豆数量,然后累加就可以了。需要考虑的是如何计算出每一列能够承载的青豆数量。方法一使用暴力法,分别向前向后遍历一次height数组,找到最高的两根柱子,这两根柱子就是承载该列青豆的容器。然后考虑“木桶原理”,我们需要比较两根柱子的长短,其中较短的那一根柱子能够决定容器的容量,将容器的容量减去当前列柱子的长度就能得到当前列能承载的青豆数量了。
时间复杂度:每一列都要分别计算青豆数量,每次计算遍历所有的列,因此时间复杂度是O(N^2)。

func trap(height []int) int {
    res := 0
    for i := 1; i < len(height) - 1; i++ {
        lmax, rmax := 0, 0
        for j := i - 1; j >= 0; j-- {
            lmax = max(height[j], lmax)
        }
        for j := i + 1; j < len(height); j++ {
            rmax = max(height[j], rmax)
        }
        if min(lmax, rmax) > height[i] {
            res += min(lmax, rmax) - height[i]
        }
    }
    return res
}

方法二:动态规划

方法一在获取每列的容器(左右最高的柱子)时会把整个height遍历一次,这里存在着优化的空间。
我们需要使用两个长度为n的数组,分别记录每一列左边和右边最高的柱子,比如lHeightMax[i]表示第i列左边最高的柱子高度,rHeightMax[i]表示第i列右边最高的高度。有了这两个数组,我们在求某一列的容器大小的时候,就不用遍历整个heigh数组了。
时间复杂度:整个过程总共需要遍历三次height数组,时间复杂度是O(N)。

func trap(height []int) int {
   n := len(height)
   res := 0
   lHeightMax := make([]int, n)
   rHeightMax := make([]int, n)
   maxTmp := 0
   for i := 0; i < n; i++ {
      maxTmp = max(height[i], maxTmp)
      lHeightMax[i] = maxTmp
   }
   maxTmp = 0
   for i := n - 1; i >= 0; i-- {
      maxTmp = max(height[i], maxTmp)
      rHeightMax[i] = maxTmp
   }
   for i := 1; i < n-1; i++ {
      if min(lHeightMax[i], rHeightMax[i]) > height[i] {
         res += min(lHeightMax[i], rHeightMax[i]) - height[i]
      }
   }
   return res
}