【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
相关推荐
lwx572801 小时前
探秘InnoDB:搞懂它的内存、线程、磁盘与日志刷盘策略
java·后端
Flynt3 小时前
从Spring Boot 4.0升到4.1,我在Maven和gRPC上栽了跟头
java·spring boot·后端
plainGeekDev4 小时前
Activity 间传值 → Navigation 参数
android·java·kotlin
plainGeekDev4 小时前
onActivityResult → ActivityResult API
android·java·kotlin
Sunia4 小时前
《AgentX 专栏》10-生产部署:3台2C4G云服务器把企业级Agent真正跑起来的完整方案
java·架构
ZhengEnCi5 小时前
J7A-高级Java工程师面试三道灵魂拷问-深度广度与工程素养的终极检验
java·后端
_清歌8 小时前
DSpark 深度解读:DeepSeek-V4 如何用「半自回归」把推理速度提升 85%
算法
统计实现局8 小时前
SVD 的三步走:双对角化、Givens 收敛、排序
算法
躬行见万象8 小时前
《VLA 系列》UniLab 强化训练 | G1 机器人 |复现
算法