字节青训营x码上掘金

38 阅读2分钟

当青训营遇上码上掘金

参加「青训营 X 码上掘金」主题创作活动,我选择了主题四,因为我自己擅长的是后端方向。

主题四是对应了一个很经典的接雨水问题。

image.png

这道题目有多种解题方案,比如:

  • 按照行来计算青豆数
  • 按照列来计算青豆数
  • 还有使用动态规划方法来实现
  • 还有使用单调栈来实现

从思路上来说: 我们对于下标 i,下雨后水能到达的最大高度等于下标 i 两边的最大高度的最小值,下标 i 处能接的雨水量等于下标 i 处的水能到达的最大高度减去 height[i] 那么如何高效地计算出下标i 两边的最大高度的最小值 成为了这题的重点,如果我们采用暴力遍历的方法,那么时间复杂度将会达到O(n2)O(n^2), 因为对于每一个i,都需要左右遍历找。这并不是一个好的方案。

动态规划,按照列求答案:

我们可以维护两个最大高度的数组leftmax,rightmax。

leftmax[0]=hegiht[0],rightmax[n1]=height[n1]leftmax[0] = hegiht[0], rightmax[n-1] = height[n-1]. 对于其他的值,我们采用以下递推公式:

leftmax[i]=max(leftmax[i1],height[i]);(1<=i<=n1)rightmax[i]=max(rightmax[i+1],height[i]);(0<=i<=n2)leftmax[i] = max(leftmax[i-1], height[i]); ( 1 <= i <= n-1) \\ rightmax[i] = max(rightmax[i+1], height[i]); ( 0 <= i <= n-2)

然后我们就能够根据 minleftmax[i],rightmax[i])height[i],min(leftmax[i], rightmax[i]) - height[i], 累计起来,就是我们要的答案了。

单调栈:

维护一个单调栈,单调栈存储的是下标,满足从栈底到栈顶的下标对应的数组 height 中的元素递减。

单调栈实际上就是去找到了一个凹陷左边的部分,即对于坐标 i,i左边能够形成一个凹陷来存储青豆(起码两个) 能够入栈的原则是height[i] < heght[stack[-1]]. 如果不小于,则说明存在一个存储青豆的区域了。 那么高度就是 min(heght[stack[-1]](左), heght[i](右)) - heght[stack[-1]] * i - left - 1 (宽度)

最终实现代码为:

from typing import List
def main(height: List[int]) -> None:
    # 使用单调栈
      ans = 0
      stack = list()
      n = len(height)
      for i, h in enumerate(height):
          while stack and h > height[stack[-1]]:
              top = stack.pop()
              if not stack:
                  break
              left = stack[-1]
              w = i - left - 1
              currHeight = min(height[left], height[i]) - height[top]
              ans += w * currHeight
          stack.append(i)  
      return ans
if __name__ == '__main__':
  print(main([5,0,2,1,4,0,1,0,3]))