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

227 阅读4分钟

Tips为了更好地整合文章,我已经将本文章收录至专栏“字节跳动青训营”,专栏内有其他方向的内容,感兴趣的小伙伴可以关注一下专栏。

当青训营遇上码上掘金

题目:攒青豆

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

725ef710a59944d49d0315bece7a1ac1~tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.webp

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

解题思路

解法一:暴力解法

方法思路

暴力解法的思路是对于每一个柱子,暴力地向左右扩展找到左右两边第一个比它高的柱子,并计算出被这两根柱子围成的区域能够接住的青豆数量。

思路流程图

graph LR
A[柱子序列] -- 遍历 --> B[当前柱子]
B -- 左扩展 --> C[左边第一个比它高的柱子]
B -- 右扩展 --> D[右边第一个比它高的柱子]
C --> E[计算青豆数量]
D --> E
E --> A

代码示例

def trap(height):
    n = len(height)
    res = 0
    for i in range(1, n - 1):
        left_max = max(height[:i])
        right_max = max(height[i:])
        res += min(left_max, right_max) - height[i]
    return res

解法二:双指针

方法思路

可以使用双指针法来解决这个问题。

  • 首先,寻找整个柱子中最高的柱子,并将柱子分为左右两部分。
  • 然后,使用两个指针分别指向左右两部分的最左边和最右边的柱子。
  • 接下来,比较左右指针所指向的柱子的高度,并将指针移动到较短的柱子的右边。 在指针移动过程中,累加所有被柱子包含的青豆数量。
  • 最终,累加器中的值即为所求答案。

代码示例

def trap(height):
    if not height:
        return 0
    n = len(height)
    left, right = 0, n - 1
    left_max, right_max = height[left], height[right]
    res = 0
    while left < right:
        if height[left] <= height[right]:
            if height[left] >= left_max:
                left_max = height[left]
            else:
                res += left_max - height[left]
            left += 1
        else:
            if height[right] >= right_max:
                right_max = height[right]
            else:
                res += right_max - height[right]
            right -= 1
    return res

解法三:栈

方法思路

使用栈解题思路是,对于每一个柱子,将其与栈中的柱子进行比较,如果当前柱子比栈顶柱子高,则弹出栈顶元素并计算出被这两根柱子围成的区域能够接住的青豆数量,然后继续比较。

代码示例

def trap(height):
    res = 0
    stack = []
    for i in range(len(height)):
        while stack and height[i] > height[stack[-1]]:
            top = stack.pop()
            if not stack:
                break
            distance = i - stack[-1] - 1
            bounded_height = min(height[i], height[stack[-1]]) - height[top]
            res += distance * bounded_height
        stack.append(i)
    return res

解法四:动态规划

方法思路

使用动态规划解题的思路是,对于每一个柱子,计算出它左边最高柱子和右边最高柱子,并计算出被这两根柱子围成的区域能够接住的青豆数量。

代码示例

def trap(height):
    n = len(height)
    left_max, right_max = [0] * n, [0] * n
    left_max[0] = height[0]
    for i in range(1, n):
        left_max[i] = max(left_max[i-1], height[i])
    right_max[n-1] = height[n-1]
    for i in range(n-2, -1, -1):
        right_max[i] = max(right_max[i+1], height[i])
    res = 0
    for i in range(n):
        res += min(left_max[i], right_max[i]) - height[i]
    return res

解法对比

算法时间复杂度空间复杂度优点缺点
双指针O(N)O(1)空间复杂度低,时间复杂度优秀需要额外维护两个指针
O(N)O(N)简单易懂,不需要额外空间时间复杂度略高于双指针
暴力O(N2)O(N^2)O(1)实现简单时间复杂度高,不适用于数据规模较大的情况
动态规划O(N)O(N)简单易懂,时间复杂度优秀空间复杂度较高

文章完整代码&结尾

见码上掘金代码片段: (使用了断言,点击运行无输出说明正确,可以点击python查看代码) 如果你有其他的思路或者更好的解法,亦或者你发现了文章出现了错误或有不足,欢迎在评论区和我交流,我看到了一定会回复。

写文章不易,如果你觉得文章对你有帮助,麻烦点一下点赞、收藏,你的支持是我写文章的最大动力! b54ac82fc1b4dec830e3995ee3ad92e.jpgc9895d976d1772e420c80b8171e6eb1.jpgfe6de72313429bb4c4b2449c7e16ebc.jpg

最后,祝大家兔年快乐!