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

35 阅读5分钟

青训营 X 码上掘金 主题创作

当青训营遇上码上掘金

本文主要是在参加第五届字节跳动青训营后端基础班 攒青豆 学本领期间,参与 「青训营 X 码上掘金」主题创作 活动 主题 4 :攒青豆的解题过程记录。

文章预计包括四个部分:

    1. 执行环境
    1. 题目描述
    1. 题目解析
    1. 代码

执行环境

代码环境

码上掘金

也可以在【稀土掘金】-【创作者中心】点击【写代码】进入【码上掘金】界面,非常方便!

image.png

界面简洁,非常方便我们记录一些可复用的代码小片段。

image.png

编程语言

Python

主题四:攒青豆

题目来源:

青训营官方账号:juejin.cn/post/717498…

题目描述:

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

题目解析

使用三种了解题方式。

暴力穷举法

可以看到只有柱子高低错落不齐才会形成凹口,积攒青豆;

求青豆的数量,即图中绿色的部分,我们把这道题看成求不规则的平面图形的面积,将这个不规则的图形按列进行划分。

image.png

分割后,每列青豆柱子的宽度一定,为单位 1,高度为这个柱子左右两边最高柱子中的最矮的那个柱子的高度以及这个柱子本身的高度决定的,即:

柱子 i 上青豆的高度=min(柱子 i 左边所有柱子中最高的柱子高度,柱子 i 右边所有柱子中最高的柱子高度)-柱子 i 的高度

qheight=min(max(height(i左边的所有柱子)),max(i右边的所有柱子))height(i)q_height = min(max(height(i 左边的所有柱子)),max(i右边的所有柱子))-height(i)

因此:从左到右依次遍历每个柱子(数组中的每个元素),求出每个青豆柱子的面积,再将所有的青豆柱子的面积累加就可以了。

以题目中给的例子来看,输入:height = [5,0,2,1,4,0,1,0,3] ,共 9(n)个柱子,我们将 9(n)个柱子上的青豆,当然最两端的柱子上无论多高都是不会有青豆的,因此,只需要计算 7 (n-2)个柱子上的青豆数量,进行相加。

数组索引从 i=1 开始遍历,到 i=n-2 结束,(其实,i=0,遍历到 i=n-1 也是可以的)

即,每个柱子需要各算一遍左右最高的柱子;

#从左到右依次遍历每个柱子上的青豆数
  for i in range(n):
    lmax=rmax=0
    #寻找 i 所有左边的最高柱子
    for j in range(0,i):
      lmax=max(lmax,height[j])
    #寻找 i 所有右边的最高柱子
    for j in range(i,n):
      rmax=max(rmax,height[j])
    #只有 i 左右两边最高的柱子的最低的柱子比 i 处的高度高,才会形成凹坑
    if min(lmax,rmax)>height[i]:
      ans+=min(lmax,rmax)-height[i]

无论哪种题目,感觉都可以暴力穷举法无脑求解。虽然这种方法比较费时费力,但是对于算法小白来说特别容易理解,过程不是最优,但是确实也能得出结果,算是比较保守的做法。

双指针

双指针是穷举法的进一步优化,具体理解是:

  1. 定义两个指针,从左右两边向中间遍历;可以理解为索引。
  2. 定义两个变量,记录左右两边的最高柱子的高度。
  3. 比较这两个记录最高值的变量,哪个变量值小,就用哪边的指针开始攒青豆。
  4. 遇到比这边最高值矮的柱子,就用青豆把高度差填满,否则更换这边变量的最高值。

用双指针的思路,有三种情况

  • 最高柱子在最左边。从右边指针开始遍历
  • 最高柱子在最右边。从左边指针开始遍历
  • 最高柱子在中间。在遇到最高柱子之前从左右两边轮流遍历。

比如:例题中最高柱子在最左边,因此,青豆柱子的计算顺序如图所示:

image.png

while left<=right:
    lmax=max(lmax,height[left])#左边最高的柱子
    rmax=max(rmax,height[right])#右边最高的柱子
    #左右哪边最高的柱子矮,先从哪边的指针开始攒青豆
    if lmax<rmax:
      ans+=lmax-height[left]#高度差攒豆子
      left+=1
    else:
      ans+=rmax-height[right]
      right-=1

代码

完整代码:code.juejin.cn/pen/7199674…

#主题四:攒青豆
#解法一:穷举法
def sumbean(height:list[int]) -> int:
  #边界条件
  if not height:return 0
  ans=0#青豆总数
  n=len(height)#柱子个数
  #从左到右依次遍历每个柱子上的青豆数
  for i in range(n):
    lmax=rmax=0
    #寻找 i 所有左边的最高柱子
    for j in range(0,i):
      lmax=max(lmax,height[j])
    #寻找 i 所有右边的最高柱子
    for j in range(i,n):
      rmax=max(rmax,height[j])
    #只有 i 左右两边最高的柱子的最低的柱子比 i 处的高度高,才会形成凹坑
    if min(lmax,rmax)>height[i]:
      ans+=min(lmax,rmax)-height[i]

  return ans

#解法二:双指针
def trap(height:list[int]) -> int:
  #边界条件
  if not height:return 0
  #定义两个指针
  left,right=0,len(height)-1
  #定义两个变量
  lmax=height[0]
  rmax=height[len(height)-1]
  #left_max=right_max=0
  #攒青豆数
  ans=0
  while left<=right:
    lmax=max(lmax,height[left])#左边最高的柱子
    rmax=max(rmax,height[right])#右边最高的柱子
    #左右哪边最高的柱子矮,先从哪边的指针开始攒青豆
    if lmax<rmax:
      ans+=lmax-height[left]#高度差攒豆子
      left+=1
    else:
      ans+=rmax-height[right]
      right-=1

  return ans

其他解法:单调栈、动态规划、面积法,就先不讨论的叭(对我来说有点难度)。。

另外,攒青豆与力扣上接水滴 灰常类似,想深一步研究可以参考学习。

本题给的是二维平面,也有三维立体攒青豆的情况,值得继续探究学习~