【Leetcode】 接雨水

1 题目

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

示例 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 个单位的雨水(蓝色部分表示雨水)。

示例 2:

输入:height = 4,2,0,3,2,5

输出:9

2 分析

首先,从图示可以看出,数组总的接水量等于每个位置接水量的和。而每个位置的接水量取决于它左右两边柱子的高度(准确的说,是取决于它左边最高的柱子和右边最高的柱子中较低的那个)。这样它左右两边和它自己就可以形成一个容器来盛水。因此,最暴力的解法是遍历每一个位置i,然后从这个位置出发,分别向左右两边寻找最高的柱子leftMax和rightMax,然后计算得到盛水量min(leftMax,rightMax)-heighti。

python 复制代码
def trap(self, height):
    """
    :type height: List[int]
    :rtype: int
    """
    n = len(height)
    res = 0
    for i in range(1,n-1):
        leftMax = max(height[:i])
        rightMax = max(height[i+1:])
        tmp = min(leftMax,rightMax)-height[i]
        if tmp>0:
            res += tmp
    return res

但上面这种方式时间复杂度很高,而且其实有很多重复工作。例如,对于第i+1个位置而言,其左边的leftMax其实只需要看第i个位置左边的leftMax与heighti的大小。

python 复制代码
def trap(self, height):
    """
    :type height: List[int]
    :rtype: int
    """
    n = len(height)
    res = 0
    leftMax = height[0]
    for i in range(1,n-1):
        leftMax = max(height[i-1],leftMax)
        rightMax = max(height[i+1:])
        cur = min(leftMax,rightMax)-height[i]
        if cur>0:
            res += cur
    return res

但依然超出时间限制,因为右边rightMax还是有重复工作。因此,考虑把leftMax的递归思想引入rightMax中,先从右向左计算每个位置的rightMax并用数组记录,后续再遍历计算每个位置的雨水量的时候,就可以直接取这部分信息使用了。

python 复制代码
def trap(self, height):
    """
    :type height: List[int]
    :rtype: int
    """
    n = len(height)
    res = 0
    leftMax = height[0]
    rightMax = height[n-1]
    rightMaxList = [0]*n
    for i in range(n-1,0,-1):
        rightMax = max(height[i],rightMax)
        rightMaxList[i] = rightMax

    for i in range(1,n-1):
        leftMax = max(height[i-1],leftMax)
        rightMax = rightMaxList[i+1]
        cur = min(leftMax,rightMax)-height[i]
        if cur>0:
            res += cur
    return res

上面的方法提交成功,终于不超时了,但只击败了5%。需要思考下还有没有别的优化点。

有一个关键点是,每次计算位置i的盛水量,其实只用到了leftMax和rightMax中较小的那个。如果我们明确知道位置i的右边有个很大的值,超出了其左边的最大值,那么就可以只用leftMax的信息。同理,如果我们明确知道位置i的左边有个很大的值,超出了其右边的最大值,那么就可以只用rightMax的信息。而每一个时刻,leftMax和rightMax总有大小,因此总有位置的盛水量可以被计算,不是左边就是右边。

换一种方式说,如果有两个指针left和right分别从左向右、从右向左移动,leftMax表示height:left的最大值,heightright:的最大值。那么,如果明确leftMax<rightMax,其实已经可以更新left位置的盛水量了(因为rightMax只会增大不会减少了:因为left<right,从而heightleft:>=heightright:)。同理,如果明确leftMax>rightMax,其实已经可以更新right位置的盛水量了(因为leftMax只会增大不会减少了:因为left<right,从而height:right>=height:left)。如果明确leftMax=rightMax,那么更新left和right都可以,因为它俩都有一边是会只增不减的。这样我们就可以一边移动双指针,一边更新盛水量,一遍维护leftMax和rightMax了。

python 复制代码
def trap(self, height):
    """
    :type height: List[int]
    :rtype: int
    """
    n = len(height)
    res = 0
    left, right = 1, n-2
    leftMax = height[0]
    rightMax = height[n-1]
    while left <= right:
        if leftMax < rightMax:
            cur = leftMax-height[left]
            leftMax = max(height[left],leftMax)
            left += 1
            res += max(cur,0)
        else:
            cur = rightMax-height[right]
            rightMax = max(height[right],rightMax)
            right -= 1
            res += max(cur,0)
    return res
相关推荐
aqiu11111124 分钟前
【C++算法打怪专栏】8. 字符串转换整数 (atoi)
开发语言·c++·算法
代码什么用8 小时前
Spring基础使用
java·后端·spring
信奥卷王9 小时前
[GESP202609 六级] 分树规划
算法
Su米苏9 小时前
Spring AI 中MCP 与普通 @Tool的区别
java·人工智能·spring
夏幻灵9 小时前
LeetCode 21:合并两个有序链表——双指针与哑节点详解
算法·leetcode·链表
码云数智-园园9 小时前
unique_ptr 还是 shared_ptr?C++ 智能指针选型与内存泄漏实战分析
java·开发语言
老板一杯拿铁9 小时前
Java 异常体系详解:从字节码原理到最佳实践
java
蜗牛互联网9 小时前
Java 17 HttpClient调用文件转写API的超时与失败回退
java·人工智能·后端
禾小西10 小时前
07丨Redis 哨兵机制:主库故障后,如何恢复服务?
java·开发语言·redis