• Leetcode 42.接雨水


    1.题目描述

    给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

    在这里插入图片描述

    输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
    输出:6
    解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。


    输入:height = [4,2,0,3,2,5]
    输出:9

    • 提示:
      • n == height.length
      • 1 <= n <= 2 * 104
      • 0 <= height[i] <= 105

    2.思路分析

    2.1 动态规划

    对于下标 i,下雨后水能到达的最大高度等于下标 i 两边的最大高度的最小值,下标 i 处能接的雨水量

    等于下标 i 处的水能到达的最大高度减去 height[i]

    当前列的雨水面积= min(左边柱子的最高高度, 右边柱子的最高高度) - 当前柱子的高度

    具体做法:

    • 创建两个长度为n的数组leftMax和rightMax。
      • leftMax[i]:下标i及其左边的位置中height的最大高度
      • rightMax[i]:下标i及其右边的位置中height的最大高度
    • leftMax[0] = height[0] rightMax[n-1] = height[n-1]

    l e f t M a x [ i ] = m a x ( l e f t M a x [ i − 1 ] , h e i g h t [ i ] ) i f 1 ≤ i ≤ n − 1 r i g h t M a x [ i ] = m a x ( r i g h t M a x [ i + 1 ] , h e i g h t [ i ] ) i f 0 ≤ i ≤ n − 2 leftMax[i] = max(leftMax[i-1], height[i]) \qquad if \quad 1 ≤ i ≤ n-1 \\ rightMax[i] = max(rightMax[i+1], height[i]) \qquad if \quad 0 ≤ i ≤ n-2 leftMax[i]=max(leftMax[i1],height[i])if1in1rightMax[i]=max(rightMax[i+1],height[i])if0in2

    • 正向遍历数组 height 得到数组 leftMax 的每个元素值,反向遍历数组 height 得到数组 rightMax

      的每个元素值。

    • 遍历结束之后,下标i处能接的雨水量=min(leftMax[i], rightMax[i]) - height[i]

    2.2 单调栈

    Q1:单调栈内的元素顺序?

    维护一个单调栈, 单调栈存储的元素的下标, 栈顶->栈底:递增(小->大)

    一旦发现添加的柱子高度大于栈顶元素,此时就会出现凹槽, 栈顶元素就是凹槽底部的柱子, 栈顶的第二个元素就是凹槽左边的柱子, 当前元素就是凹槽右边的柱子。
    在这里插入图片描述

    Q2:遇到相同柱子如何处理?

    遇到相同的元素,更新栈内下标,就是将栈里元素(旧下标)弹出,将新元素(新下标)加入栈中。

    举个栗子:height= [4,4,1,3], 如果添加第二个5的时候就应该将第一个5的下标弹出,把第二个5添加到栈中。
    在这里插入图片描述

    单调栈的处理逻辑:

    • 情况1:当前遍历元素高度 < 栈顶元素的高度, 当前元素入栈(保持 小->大 单调的性质 )

    • 情况2: 当前遍历元素高度 == 栈顶元素高度, 更新栈顶元素(遇到相同柱子,使用右边柱子计算高度)

    • 情况3: 当前遍历元素高度 > 栈顶元素, 此时出现凹槽, 弹出栈顶元素(凹槽底部柱子, 记为mid), 此时

      栈顶元素(stack.top())(凹槽左边的柱子, height[st.top()]), 当前元素记为凹槽右边的柱子(height[i])

      • 雨水的高度: h = min(height[st.top()], height[i]) - height[mid]
      • 雨水的宽度: w = i - stack.top() -1
      • 凹槽的体积:h * w

    2.3 双指针

    维护两个指针 left 和 right, 以及两个变量 leftMax 和 rightMax, 初始时 left=0, right=n-1,

    leftMax=0,rightMax=0。指针 left 只会向右移动,指针 right 只会向左移动,在移动指针的过程中

    维护两个变量 leftMax 和 rightMax 的值。

    当两个指针没有相遇时,

    • 使用 height[left] 和height[right] 的值更新 leftMax 和rightMax 的值;

    • 如果 height[left]

    • 如果 height[left]≥height[right],则必有 leftMax≥rightMax,下标right 处能接的雨水量等于 rightMax−height[right],将下标 right 处能接的雨水量加到能接的雨水总量,然后将 right 减 1(即向左移动一位)。

    当两个指针相遇时,即可得到能接的雨水总量。

    举个栗子: height=[0,1,0,2,1,0,1,3,2,1,2,1]

    • 1.left = 0 right = 11 leftMax = 0 rightMax = 1 此时height[left] < height[right], left右移一位
      在这里插入图片描述

    • 2.left = 1 right = 11 leftMax = 1 rightMax = 1 此时height[left] = height[right], right左移一位
      在这里插入图片描述

    • 3.left = 1 right = 10 leftMax = 1 rightMax = 2 此时height[left] < height[right], left右移一位
      在这里插入图片描述

    • 4.left = 2 right = 10 leftMax = 1 rightMax = 2 此时height[left] < height[right], left右移一位
      在这里插入图片描述

    • 5.left = 3 right = 10 leftMax = 2 rightMax = 2 此时height[left] = height[right], right左移一位
      在这里插入图片描述

    • 6.left = 3 right = 9 leftMax = 2 rightMax = 2 此时height[left] > height[right], right左移一位
      在这里插入图片描述

    • 7.left = 3 right = 8 leftMax = 2 rightMax = 2 此时height[left] = height[right], right左移一位
      在这里插入图片描述

    • 8.left = 3 right = 7 leftMax = 2 rightMax = 3 此时height[left] < height[right], left右移一位
      在这里插入图片描述

    • 9.left = 4 right = 7 leftMax = 2 rightMax = 3 此时height[left] < height[right], left右移一位
      在这里插入图片描述

    • 10.left = 5 right =7 leftMax = 2 rightMax = 3 此时height[left] < height[right], left右移一位
      在这里插入图片描述

    • 11.left = 6 right =7 leftMax = 2 rightMax = 3 此时height[left] < height[right], left右移一位
      在这里插入图片描述

    • 12.遍历结束,返回结果
      在这里插入图片描述

    3.代码实现

    3.1 动态规划

    class Solution:
        def trap(self, height: List[int]) -> int:
            if not height:
                return 0
            n = len(height)
            leftMax = [height[0]] + [0] * (n - 1)
            rightMax = [0] * (n - 1) + [height[n - 1]]
    		
            for i in range(1, n):
                leftMax[i] = max(leftMax[i-1], height[i])
            for i in range(n-2, -1, -1):
                rightMax[i] = max(rightMax[i+1], height[i])
    
            ans = sum(min(leftMax[i], rightMax[i]) - height[i] for i in range(n))
            return ans
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    复杂度分析

    • 时间复杂度:O(n),其中 n 是数组height 的长度。 计算数组 leftMax 和 rightMax 的元素值各需要遍历数组 height 一次,计算能接的雨水总量还需要遍历一次。
    • 空间复杂度:O(n),其中 nn 是数组 height 的长度。需要创建两个长度为 n 的数组 leftMax 和 rightMax。

    3.2 单调栈

    # 方式1
    class Solution:
        def trap(self, height: List[int]) -> int:
            if not height:
                return 0
            result = 0
            stack = [0]
    
            for i in range(1, len(height)):
                # 情况1:当前元素 < 栈顶元素
                if height[i] < stack[-1]:
                    stack.append(i)
                # 情况2:当前元素 == 栈顶元素
                elif height[i] == stack[-1]:
                    stack.pop()
                    stack.append(i)
                else:  # 情况3:当前元素 > 栈顶元素
                    while stack and height[i] > height[stack[-1]]:
                        mid_height = height[stack[-1]]
                        stack.pop()
                        if stack:
                            right_height = height[i]
                            left_height = height[stack[-1]]
                            h = min(left_height, right_height) - mid_height
                            w = i - stack[-1] - 1
                            result += h * w
                    stack.append(i)
                return result
    # 方式2
    class Solution:
        def trap(self, height: List[int]) -> int:
            stack = [0]
            result = 0
            for i in range(1, len(height)):
                while stack and height[i] > height[stack[-1]]:
                    mid_height = stack.pop()
                    if stack:
                        # 雨水高度是 min(凹槽左侧高度, 凹槽右侧高度) - 凹槽底部高度
                        h = min(height[stack[-1]], height[i]) - height[mid_height]
                        # 雨水宽度是 凹槽右侧的下标 - 凹槽左侧的下标 - 1
                        w = i - stack[-1] - 1
                        # 累计总雨水体积
                        result += h * w
                stack.append(i)
            return result
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    复杂度分析

    • 时间复杂度:O(n),其中 n 是数组 height 的长度。从 0 到 n-1 的每个下标最多只会入栈和出栈各一次。
    • 空间复杂度:O(n),其中 n 是数组 height 的长度。空间复杂度主要取决于栈空间,栈的大小不会超过 n。

    3.3 双指针

    class Solution:
        def trap(self, height: List[int]) -> int:
            ans = 0
            left, right = 0, len(height) - 1
            leftMax = rightMax = 0
    
            while left < right:
                leftMax = max(leftMax, height[left])
                rightMax = max(rightMax, height[right])
                if height[left] < height[right]:
                    ans += leftMax - height[left]
                    left += 1
                else:
                    ans += rightMax - height[right]
                    right -= 1  
            return ans
    # 方式3
    class Solution:
        def trap(self, height: List[int]) -> int:
            if not height:
                return 0
            result = 0
            stack = []
    
            for i in range(len(height)):
                while stack and height[i] > height[stack[-1]]:
                    mid_height = height[stack.pop()]
                    if stack:
                        h = min(height[stack[-1]], height[i]) - mid_height
                        w = i - stack[-1] - 1
                        result += h * w
                stack.append(i)
            return result
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    复杂度分析

    • 时间复杂度:O(n),其中 n 是数组 height 的长度。两个指针的移动总次数不超过 n。
    • 空间复杂度:O(1)。只需要使用常数的额外空间。

    参考:

    1.https://leetcode.cn/problems/trapping-rain-water/solution/jie-yu-shui-by-leetcode-solution-tuvc/

  • 相关阅读:
    小鹏汽车的“未来”故事有点性感
    深度学习_PyCharm入门
    前端框架Bootstrap
    预告|年度总决赛即将打响, 20余个项目角逐嘉兴经开区
    oracle 删除语句(时间范围)
    ROS2对比ROS1的一些变化与优势(全新安装ROS2以及编译错误处理)《1》
    linux文件查看和文件查找
    MySQL 迁移完不能快速导数据了?
    OpenText™ Exceed™ TurboX (ETX) 助力医疗保健信息系统开发商将远程访问的稳定性提高了 95%
    APP广告变现策略:如何实现盈利与用户体验的平衡?
  • 原文地址:https://blog.csdn.net/weixin_44852067/article/details/126603475