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

36 阅读2分钟

当青训营遇上码上掘金

题目介绍

现有 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 个单位的青豆。

思路

1.遍历

最简单的思路,遇到数组总会第一个想到遍历,不加任何的优化,纯纯暴力解题。虽然时间上总会稍慢,但是这也是一种开始思路。

  1. 初始化需要维护的变量 leftMax,rightMax := 0, 0
  2. 从数组的左侧遍历到当前位置寻找最大值,在找到的最大值与维护的leftMax中取大赋值给leftMax
  3. 从当前位置向数组的右侧遍历寻找最大值,在找到的最大值与维护的rightMax中取大赋值给rightMax
  4. ans = ans + min(leftMax, rightMax) - height[i]
  5. 完成一次遍历
  6. 在整个数组中重复
package main
​
import "fmt"
​
func main() {
    height := []int{5, 0, 2, 1, 4, 0, 1, 0, 3}
    fmt.Println(trap(height))
}
​
func trap(height []int) (ans int) {
    n := len(height)
    if n == 0 {
        return
    }
    for i := 1; i < n-1; i++ {
        leftMax, rightMax := 0, 0
        for j := 0; j < i; j++ {
            if height[j] > leftMax {
                leftMax = height[j]
            }
        }
        for j := i + 1; j < n; j++ {
            if height[j] > rightMax {
                rightMax = height[j]
            }
        }
        min := leftMax
        if rightMax < min {
            min = rightMax
        }
        if min > height[i] {
            ans += min - height[i]
        }
    }
    return
}
​

2. 动态规划

  • 提前存储每个位置上所有左边柱子高度的最大值和所有右边柱子高度的最大值
  • 遍历一边数组通过当前柱子的高度以及左右两侧最高的高度(使用动态规划表维护)计算出当前位置可以存储的青豆数量
package main
​
import "fmt"
​
func main() {
    height := []int{5, 0, 2, 1, 4, 0, 1, 0, 3}
    fmt.Println(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
}

3. 栈

  • 遍历数组,当当前高度小于栈顶墙高度时,说明这里可能有青豆,并且将墙入栈
  • 如果当前高度大于栈顶墙高度则可以开始计算之前有多少青豆(最左最右两个高墙之间都是青豆)
  • 将所有墙出栈,将当前墙入栈作为最左高墙
package main
​
import "fmt"func main() {
    height := []int{5, 0, 2, 1, 4, 0, 1, 0, 3}
    fmt.Println(trap(height))
}
​
func trap(height []int) (ans int) {
    n := len(height)
    if n == 0 {
        return
    }
    stack := make([]int, 0)
    for i := 0; i < n; i++ {
        for len(stack) != 0&& height[i] > height[stack[len(stack)-1]]{
            h := height[stack[len(stack)-1]]
            stack = stack[:len(stack) - 1] // 出栈
            if len(stack) == 0{
                break
            }
            dis := i - stack[len(stack)-1] - 1
            min := height[stack[len(stack)-1]]
            if height[i] < min {
                min = height[i]
            }
            ans += dis * (min - h)
        }
        stack = append(stack, i)
    }
    return
}
​