【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
相关推荐
从琳开始9891 分钟前
从Socket到UDP回显服务器:Java网络编程完整指南
java·网络·网络协议·udp·java-ee
SZYSK9 分钟前
OpenHarmony 上读卡、推理和双屏刷新抢 CPU:核验机与消费机的调度差
物联网·算法·harmonyos·1024程序员节
Ivanqhz27 分钟前
MoE + LLM 应用
数据库·人工智能·pytorch·python·深度学习·算法·1024程序员节
西瓜拿铁好喝39 分钟前
开源和闭源 AI 办公工具,分别适合谁
java·前端·数据库
学长毕设41 分钟前
基于SpringBoot的校园礼服租赁系统的设计与实现
java·spring boot·后端
lpfasd12343 分钟前
推荐算法进化史
算法·机器学习·推荐算法
明天也要努力学习1 小时前
深入理解 JVM 垃圾回收(GC)
java·jvm
Pniubi1 小时前
力扣543二叉树的直径(深度优先搜索法(递归))
算法·leetcode·职场和发展
golang学习记1 小时前
IDEA 断点调试技巧:拖拽
java·ide·intellij-idea
Sayai1 小时前
Flink 日志解析任务性能优化实战:正则/Grok 预编译、Guava 缓存权重陷阱与 ES 写入隔离
java·大数据·性能优化·flink·实时计算