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

32 阅读2分钟

当青训营遇上码上掘金

  • 主题 4:攒青豆

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

题解

对于下标 i,攒青豆后能到达的最大高度等于下标 i 两边的最大高度的最小值,下标 i 处能接的青豆量等于下标 i 处的青豆能到达的最大高度减去 height[i]。

朴素的做法是对于数组 height 中的每个元素,分别向左和向右扫描并记录左边和右边的最大高度,然后计算每个下标位置能接的青豆量。假设数组 height 的长度为 n,该做法需要对每个下标位置使用 O(n) 的时间向两边扫描并得到最大高度,因此总时间复杂度是 O(n^2)。

上述做法的时间复杂度较高是因为需要对每个下标位置都向两边扫描。如果已经知道每个位置两边的最大高度,则可以在 O(n) 的时间内得到能接的青豆总量。使用动态规划的方法,可以在 O(n) 的时间内预处理得到每个位置两边的最大高度。

创建两个长度为 n 的数组 leftMax 和 rightMax。对于 0≤i<n,leftMax[i] 表示下标 i 及其左边的位置中,height 的最大高度,rightMax[i] 表示下标 i 及其右边的位置中,height 的最大高度。

显然,leftMax[0]=height[0],rightMax[n−1]=height[n−1]。两个数组的其余元素的计算如下:

  • 当 1≤i≤n−1 时,leftMax[i]=max(leftMax[i−1],height[i]);

  • 当 0≤i≤n−2 时,rightMax[i]=max(rightMax[i+1],height[i])。

因此可以正向遍历数组 height 得到数组 leftMax 的每个元素值,反向遍历数组 height 得到数组 rightMax 的每个元素值。

在得到数组 leftMax 和 rightMax 的每个元素值之后,对于 0≤i<n,下标 i 处能接的青豆量等于 min(leftMax[i],rightMax[i])−height[i]。遍历每个下标位置即可得到能接的青豆总量。

该题与力扣接雨水为相同思路

代码

package main

import (
   "fmt"
   "strconv"
   "strings"
)

func calGreenBean(input string) int {
   input = input[10 : len(input)-1]
   split := strings.Split(input, ",")
   height := make([]int, len(split))
   for i, s := range split {
      height[i], _ = strconv.Atoi(s)
   }
   return trap(height)
}

func trap(height []int) (ans int) {
   n := len(height)
   if n == 0 {
      return
   }

   leftMax := make([]int, n)
   leftMax[0] = height[0]
   for i := 1; i < n; i++ {
      leftMax[i] = max(leftMax[i-1], height[i])
   }

   rightMax := make([]int, n)
   rightMax[n-1] = height[n-1]
   for i := n - 2; i >= 0; i-- {
      rightMax[i] = max(rightMax[i+1], height[i])
   }

   for i, h := range height {
      ans += min(leftMax[i], rightMax[i]) - h
   }
   return
}

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

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

func main() {
   fmt.Println(calGreenBean("height = [5,0,2,1,4,0,1,0,3]"))
}