青训营 X 码上掘金 --- 主题 4:攒青豆

159 阅读2分钟

当青训营遇上码上掘金

1. 问题描述

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

以下为上图例子的解析:

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

2. 题目分析

  • 通过分析可知,可以容纳青豆的关键柱子高度呈先增后减的趋势
  • 即对于height = [2,0,5,0,2,1,4,0,1,0,3]这样一个输入,最终容纳青豆的柱子为[2,5,4,3]
  • 其余的柱子会被淹没在青豆中,同时会占用青豆的空间
  • 可以先通过单调栈来确定容纳青豆的关键柱子
  • 然后根据单调栈计算关键柱子围成的空间的容积
  • 最后减去非关键柱子所占据的青豆空间

3. 解题思路

  1. 遍历height数组,确定height值最高的元素所在位置,如果有多个最大值,随便取一个即可,定义为max_idx
  2. 定义int类型变量ans保存计算的结果,ans初始化为ans=0
  3. max_idx开始向左遍历,维护一个height值单调减的索引栈stackstack中保存的是单调递减的关键柱子的索引
  4. 当栈stack空或遍历到的元素iheight值比栈顶元素对应的height值小时,索引i入栈
  5. 当遍历到的元素iheight值比栈顶元素对应的height值大时,栈顶元素出栈,出栈的同时ans减去出栈元素对应的height值,表示非关键柱子占据的豆子空间
  6. 遍历stack,计算关键柱子所围出的空间,计算结果加到ans中。
  7. max_idx开始向右遍历,重复第3步第6步,计算max_idx右侧所能容纳的豆子数,加到ans
  8. 返回ans的值,计算结束

4. 代码实现

def save_green_beans(nums) -> int:
  n = len(nums)
  max_idx = 0
  # 找到值最大的 index,将 index 赋值给 max_idx
  for i in range(n):
    if nums[i]>nums[max_idx]:
      max_idx = i

  ans = 0  # 保存结果

  # 从 max_idx 开始向左遍历,维护一个单调减的栈,出栈的同时ans减去出栈元素的值,表示比较低的柱子占据的豆子空间
  stack = []
  for i in range(max_idx, -1, -1):
    while len(stack) and nums[stack[-1]]<nums[i]:
      ans -= nums[stack[-1]]
      stack.pop()
    stack.append(i)
  # 根据单调栈计算围出的空间,并加到ans
  for i in range(len(stack)-1):
    ans += nums[stack[i+1]]*(stack[i]-stack[i+1]-1)

  # 从 max_idx 开始向右遍历,维护一个单调减的栈,出栈的同时ans减去出栈元素的值,表示比较低的柱子占据的豆子空间
  stack = []
  for i in range(max_idx, n):
    while len(stack) and nums[stack[-1]]<nums[i]:
      ans -= nums[stack[-1]]
      stack.pop()
    stack.append(i)
  # 根据单调栈计算围出的空间,并加到ans
  for i in range(len(stack)-1):
    ans += nums[stack[i+1]]*(stack[i+1]-stack[i]-1)
  
  # 返回结果
  return ans
  

def main() -> None:
  nums = [5,0,2,1,4,0,1,0,3]
  ans = save_green_beans(nums)
  print(ans)

if __name__ == '__main__':
  main()

5. 复杂度分析

  • 对于height数组中的元素个数N,时间复杂度为O(N),空间复杂度为O(N)