算法题:攒青豆

36 阅读3分钟

当青训营遇上码上掘金,使用官方推广码上掘金平台,将代码上传到浏览器上并运行。

题目描述

  • 主题 4:攒青豆

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

image.png

以下为上图例子的解析:

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

思路

经常刷题的童鞋应该已经看出来了,其实就是翻版的42. 接雨水 - 力扣(Leetcode),个人觉得用单调栈来解决比较简单。

维护一个单调栈,单调栈存储的是下标,要求下标对应元素在栈中是单调递减的。

从左到右遍历数组,若栈空则下标值入栈,若当前元素小于栈顶元素则下标值入栈;

若当前元素大于栈顶元素,还需要比较栈顶的第二个元素。若第二个元素小于当前元素,则可以计算该区域内可堆积的青豆,然后弹出栈顶元素,继续之前的步骤。直到第二个元素大于当前元素,或栈中已经没有元素了,则计算青豆,并将当前元素入栈。

其实上面那段思路很多地方都有不同的描述,但是一般这样的描述都比较抽象我自己也不太愿意看,需要自己慢慢理顺了。这里用一张图标出根据单调栈计算面积的次序,可以更好的理解代码。

image.png

代码

collectGreenPeas - 码上掘金 (juejin.cn)

这里不知道怎么在码上掘金平台控制输入。这里严格按照输入为height = [5,0,2,1,4,0,1,0,3]处理,此外由于以前算法题ACM模式都是用c++处理,这里尝试用golang处理,使用bufio包,不知道还有没有更好的写法。

package main

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

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

func peas(height []int) (ans int) {
	stack := []int{}
	for index, h := range height {
		for len(stack) > 0 && h > height[stack[len(stack)-1]] {
			top := stack[len(stack)-1]
			stack = stack[:len(stack)-1]
			if len(stack) == 0 {
				// 剩余栈空, 则无法装青豆
				break
			}
			// 计算一次不高于当前桶最左端可装的青豆
			left := stack[len(stack)-1]
			curWidth := index - left - 1
			curHeight := min(height[left], h) - height[top]
			ans += curWidth * curHeight
		}
		// 若栈空 或 当前元素小于栈顶元素,则将当前元素的index入栈
		stack = append(stack, index)
	}
	return
}

func main() {
	input := bufio.NewReader(os.Stdin)
	line, err := input.ReadString('\n')
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(len(line))
	// windows需要去除字符串尾部的\r\n换行符
	line = strings.Trim(line, "\r\n")
	// 去除方括号
	line = string([]byte(line)[10 : len(line)-1])
	// 分割字符串
	strDatas := strings.Split(line, ",")
	// 转换为数组
	datas := make([]int, len(strDatas))
	for i := 0; i < len(strDatas); i++ {
		datas[i], err = strconv.Atoi(strDatas[i])
		if err != nil {
			log.Fatal(err)
		}
	}
	fmt.Println(peas(datas))
}