[LeetCode]42. 接雨水

题目

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

在这里插入图片描述
上面是由数组 [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

解题思路

这题有点类似木桶效应,一个木桶的盛水量是由最低的那块板子决定的。
可以把每个柱子分别想成一个木桶,那么每个柱子高度方向的盛水量则是由从当前柱子向左看的最高柱子高度与从当前柱子向右看的最高柱子高度中的较低一方以及柱子本身高度决定的。形式化地表达第i个柱子的盛水量为 water[i] = min(leftmax[i], rightmax[i]) - height[i],

解法一:暴力(超时)

对于每根柱子,分别向左和向右找出最大的柱子高度,那么当前柱子的盛水量等于两边最大高度的较小值减去当前高度。
复杂性分析:
时间复杂度:O(n^2)。数组中的每个元素都需要向左向右扫描。
空间复杂度:O(1)。不需要额外空间。

解法二:动态规划

步骤:
1)两个数组leftmax、rightmax分别保存:从左向右遍历到下标i时最高柱子高度,和从右向左遍历到下标i时最高柱子高度。
2)再遍历一遍每个位置,只有当height[i]的高度,比leftmax[i]和rightmax[i]都要小的时候才能接住雨水(否则总有一边会漏,接不到雨水),计算当前柱子方向可以接的雨水量
3)将所有可以接到的雨水量累加起来。
复杂性分析:
时间复杂度:O(n)。存储最大高度数组,需要两次遍历,每次 O(n)。最终使用存储的数据来更新 res,时间复杂度为 O(n)。
空间复杂度:O(n)。用了额外的 O(n) 空间用来放置 leftmax 和 rightmax 数组。

解法三:双指针

从动态编程方法的示意图中我们注意到,只要 rightmax[i]>leftmax[i],积水高度将由 leftmax 决定;类似地当 leftmax[i]>rightmax[i],积水高度将由 rightmax 决定。因此,我们不需要从左和从右分开计算,一次遍历即可完成。
所以我们可以认为如果一端有更高的条形块(例如右端),积水的高度依赖于当前方向的高度(从左到右)。当我们发现另一侧(右侧)的条形块高度不是最高的,我们则开始从相反的方向遍历(从右到左)。
当我们从左往右处理到left下标时,左边的最大值leftmax对它而言是可信的,但rightmax对它而言是不可信的。
当我们从右往左处理到right下标时,右边的最大值rightmax对它而言是可信的,但leftmax对它而言是不可信的。
对于位置left而言,它左边最大值一定是leftmax,右边最大值可能会“大于等于”rightmax。这时候,如果leftmax<rightmax成立,那么它就知道自己能存多少水了。无论右边将来会不会出现更大的rightmax,都不影响这个结果。 所以当leftmax<rightmax时,我们就去处理left下标;反之,我们就去处理right下标。
复杂性分析:
时间复杂度:O(n)。单次遍历的时间O(n)。
空间复杂度:O(1) 的额外空间。left, right, leftmax 和 rightmax 只需要常数的空间。

解法四:单调栈

以上三种解法都是按列求的,单调栈是按行求的。
简单来说就是当前柱子如果小于等于栈顶元素,说明形不成凹槽,则将当前柱子入栈;反之若当前柱子大于栈顶元素,说明形成了凹槽,于是将栈中小于当前柱子的元素pop出来,将凹槽的大小累加到结果中。
因此这道题目可以用单调栈来做。单调栈就是比普通的栈多一个性质,即维护栈内元素单调。比如单调递减栈的元素从栈底到栈顶分别是:[10, 9, 8, 3, 2],如果要入栈元素5,需要把栈顶元素pop出去,直到满足单调递减为止,即先变成[10, 9, 8],再入栈5,就是[10, 9, 8, 5]。
具体做法是:
1)遍历数组,如果当前元素满足单调递减关系,则将它入栈。
2)如果遍历到当前元素大于栈顶元素,说明形成了凹槽,可以接住雨水,那么对栈顶元素进行逐个弹出操作,直到栈顶元素大于等于当前元素,或者栈空。每次弹出操作形成的凹槽的高度是此时栈顶元素和当前元素的min与弹出之前栈顶元素形成的高度差,即 h = min(top, cur) - top_,宽度则是栈顶元素的索引与当前元素索引的距离。
3)之后再将当前元素入栈,重复1与2的步骤
4)最后每个凹槽部分的雨水量累加在一起就是总的雨水量。
复杂性分析:
时间复杂度:O(n)。单次遍历 O(n),每个柱子最多访问两次(由于栈的弹入和弹出),并且弹入和弹出栈都是 O(1)的。
空间复杂度:O(n)。栈最多在阶梯型或平坦型条形块结构中占用 O(n) 的空间。

代码

解法一:暴力(超时)

class Solution:
    def trap(self, height: List[int]) -> int:
            size = len(height)
            if size == 0:
                return 0
            res = 0
            for i in range(1, size-1): # 分别找出当前柱子左右两边的最大值
                leftmax = 0
                rightmax = 0
                for j in range(i,-1,-1): # 向左 
                    leftmax = max(leftmax, height[j])
                for j in range(i, size): # 向右
                    rightmax = max(rightmax, height[j])
                if min(leftmax, rightmax) > height[i]: # 其实这句不写也没事,基于上面的判断,是一定成立的
                    res += min(leftmax, rightmax) - height[i]
            return res

解法二:动态规划

class Solution:
    def trap(self, height: List[int]) -> int:
        size = len(height)
        if size == 0:
            return 0
        leftmax = [0] * size
        rightmax = [0] * size
        res = 0
        left_max = 0
        right_max = 0
        for i in range(size):
            if height[i] > left_max:
                left_max = height[i]
            leftmax[i] = left_max
            if height[size-1-i] > right_max:
                righ_tmax = height[size-1-i]
            rightmax[size-1-i] = right_max
        for i in range(size):
            if height[i] < min(leftmax[i], rightmax[i]): # 其实这句不写也没事,基于上面的判断,是一定成立的
                res += min(leftmax[i], rightmax[i]) - height[i]
        return res

解法三:双指针

class Solution:
    def trap(self, height: List[int]) -> int:
            size = len(height)
            if size == 0:
                return 0
            leftmax = 0
            rightmax = 0
            left = 0
            right = size - 1
            res = 0
            while left <= right:
                if leftmax < rightmax: # 只依赖左边
                    leftmax = max(leftmax, height[left])
                    res += leftmax - height[left]
                    left += 1
                else: # 只依赖右边
                    rightmax = max(rightmax, height[right])
                    res += rightmax - height[right]
                    right -= 1
            return res

解法四:单调栈

class Solution:
    def trap(self, height: List[int]) -> int:
            size = len(height)
            if size == 0:
                return 0
            stack = []
            res = 0
            # 遍历每个柱子
            for i in range(size):
                while stack and height[stack[-1]] < height[i]: # 如果栈不为空,且栈顶元素小于当前元素,说明可以形成凹槽
                    bottomid = stack.pop()
                    while stack and height[stack[-1]] == height[bottomid]: # 如果栈顶元素一直相等,那么全都pop出去,只留第一个。
                        stack.pop()
                    if stack:
                        h = min(height[stack[-1]], height[i]) - height[bottomid] # 凹槽高度
                        w = i - stack[-1] - 1 # 凹槽宽度
                        res += h*w
                stack.append(i)
            return res
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值