攒青豆题解——分治「青训营 X 码上掘金」主题创作活动入营版

68 阅读2分钟

当青训营遇上码上掘金

代码块中包含大量注释,也是文字

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

以下为上图例子的解析(图见下方链接):

输入:height = [5,0,2,1,4,0,1,0,3]

输出:17

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

作者:青训营官方账号

链接:juejin.cn/post/718775…

来源:稀土掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

输入格式:
5 0 2 1 4 0 1 0 3
0 1 0 2 1 0 1 3 2 1 2 1
4 2 0 3 2 5
2 0 2

输出格式:
17
6
9
2

类似木桶理论即短板效应,次大值制约了最大值的发挥。

n=height.length, 初始时begin=0, end=n-1

找出区间[begin, end]之内的最大值max1及其索引max1i,次大值max2及其所有索引max2is中的最小值nb、最大值ne。然后根据max1inbne这3个索引将区间[begin, end]分割为[begin, p1](递归), [p1, p2](可计算青豆数量), [p2, end](递归),分而治之。

  1. max1 = max2

     max1i在max2is中,即nb<=max1i<=ne.
     最大值在区间[nb, ne]内,则[nb, ne]可计算青豆数量,区间分割为[begin, nb], [nb, ne], [ne, end]
  2. max1 > max2

    Ⅰ. max1i < nb

     最大值在区间[nb, ne]外,则[max1i, ne]可计算青豆数量,区间分割为[begin, max1i], [max1i, ne], [ne, end]

    Ⅱ. max1i > ne

     最大值在区间[nb, ne]外,则[nb, max1i]可计算青豆数量,区间分割为[begin, nb], [nb, max1i], [max1i, end]

    Ⅲ. nb < max1i < ne

     最大值在区间[nb, ne]内,则[nb, ne]可计算青豆数量,区间分割为[begin, nb], [nb, ne], [ne, end]
package main

// import "fmt"

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
    "strings"
)

func main() {
    // height := []int{5,0,2,1,4,0,1,0,3}
    // ret := dc(height, 0, len(height)-1)
    // fmt.Println(ret, height)

    // 从标准输入读取 height 数组
    scanner := bufio.NewScanner(os.Stdin)
    for scanner.Scan() {
            in := strings.Split(scanner.Text(), " ")
            height := []int{}
            for _, item := range in {
                    num, _ := strconv.Atoi(item)
                    height = append(height, num)
            }
            ret := dc(height, 0, len(height)-1)
            // fmt.Println(ret, height)
            fmt.Println(ret)
    }
}

// divide and conquer,分治。
// [begin, end]内能攒的青豆
func dc(height []int, begin int, end int) int {
    if begin >= end {
            return 0
    }
    // 最大值、次大值
    max1, max2 := height[begin], -1
    max1i := begin
    for i := begin + 1; i <= end; i++ {
            if max1 <= height[i] {
                    max2 = max1
                    max1, max1i = height[i], i
            } else if max2 < height[i] {
                    max2 = height[i]
            }
    }

    // 次大值的所有索引
    max2is := []int{}
    for i := begin; i <= end; i++ {
            if height[i] == max2 {
                    max2is = append(max2is, i)
            }
    }

    ret := 0
    nb, ne := max2is[0], max2is[len(max2is)-1]
    // p1, p2是2个分割点,从max1i、nb、ne中选出
    // 分割成[begin, p1] [p1, p2] [p2, end]三段
    var p1, p2 int
    // max1 >= max2
    // max1在[nb, ne]里面
    if max1 == max2 {
            p1, p2 = nb, ne
    // max1 > max2
    // max1在[nb, ne]外面
    } else if max1i < nb {
            p1, p2 = max1i, ne
    // max1在[nb, ne]外面
    } else if ne < max1i {
            p1, p2 = nb, max1i
    // max1在[nb, ne]里面
    } else {
            p1, p2 = nb, ne
    }
    ret += dc(height, begin, p1)
    ret += comp(height, p1, p2, max2)
    ret += dc(height, p2, end)
    // fmt.Println(begin, end, max1, max1i, max2, max2is, ret)
    return ret
}

// 计算区间内能接到的青豆
func comp(height []int, begin int, end int, target int) int {
    ret := 0
    for i := begin; i <= end; i++ {
            if height[i] < target {
                    ret += target - height[i]
            }
    }
    return ret
}