接雨水问题

举报
程序员学长 发表于 2022/01/25 22:59:07 2022/01/25
【摘要】 接雨水问题读前福利,送大家一些电子书42. 接雨水 问题描述给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。示例:输入:height = [4,2,0,3,2,5]输出:9解释:上面是由数组 [4,2,0,3,2,5] 表示的高度图,在这种情况下,可以接 9 个单位的雨水(蓝色部分表示雨水)。 分析问题我们最直观的想法就是,对于数组中的每个...

接雨水问题

读前福利送大家一些电子书

42. 接雨水

问题描述

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

示例:

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

输出:9

image-20210920105647798

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

分析问题

我们最直观的想法就是,对于数组中的每个元素,下雨后能存水的高度等于它两边最大高度的较小值减去当前高度的值。比如对于数组中第二个元素,它能储水的高度就是min(maxleft,maxright)-height[1]=4-2=2

对于求它两边的最大高度,我们可以以当前元素为基准,分别进行向左和向右扫描来求得。

下面我们来看一下代码实现。

def result(height):
    ans=0
    n=len(height)
    for i in range(1,n):
        maxleft=0
        maxright=0
        
        ##向左扫描
        for j in range(i, -1, -1):
            maxleft=max(maxleft,height[j])

        ##向右扫描
        for j in range(i,n):
            maxright=max(maxright,height[j])

        ans=ans+min(maxleft,maxright)-height[i]

    return ans

height=[4,2,0,3,2,5]
print(result(height))

我们可以很明显的看到,对于数组中的每一个元素,我们都需要向左和向右进行扫描,所以这个算法的时间复杂度为O(n^2)。

优化

我们可以发现在上个算法中,我们对于数组中的每个元素都需要花费O(n)的时间去向左和向右扫描,去寻找两边的最大值。如果我们可以提前知道每个位置对应的两边的最大高度,则可以在O(n)的时间内去求得雨水的总量。

下面我们来看一下如何通过预处理来得到每个元素对应的两边的最大高度。

我们先来看一下如何求每个位置对应的左边的最大高度,对于元素height[i]来说,leftmax要么是它本身,要么是heght[i-1]对应的leftmax的值,即leftmax[i]=max(leftmax[i-1],height[i]),其中**leftmax[i]**表示下标i及其左边的位置中,height的最大高度。

同理可以求得,rightmax[i]=max(rightmax[i+1],height[i]),其中**rightmax[i]**表示下标i及其右边的位置中,height的最大高度。

下面我们来看一下代码如何实现。

def result(height):
    if not height:
        return

    n=len(height)
    ##求出左边最大
    leftmax=[0]*n
    leftmax[0]=height[0]
    for i in range(1,n):
        leftmax[i]=max(leftmax[i-1],height[i])

    rightmax=[0]*n
    rightmax[n-1]=height[n-1]
    for i in range(n-2,-1,-1):
        rightmax[i]=max(rightmax[i+1],height[i])

    ans = 0
    for i in range(0,n):
       ans=ans+min(leftmax[i],rightmax[i])-height[i]
    return ans

height=[4,2,0,3,2,5]
print(result(height))

通过代码,我们可以清楚的知道,这个算法的时间复杂度为O(n),空间复杂度也为O(n)。

终极解法

优化算法中,我们引入了两个数组leftMaxrightMax来保存每个位置对应的两边的最大高度,因此空间复杂度为O(n),那我们有什么方法可以来降低空间复杂度吗?

我们可以观察到,对于下标i处能接的雨水量是由leftMax[i]rightMax[i]中的最小值决定。由于数组leftMax是从左往右计算,而数组rightMax是从右往左计算,所以我们可以使用双指针和两个变量来替代这两个数组,下面我们来看一下。

我们维护两个指针left和right,以及两个变量leftMax和rightMax,初始时left=0,right=n-1,leftMax=0,rightMax=0。指针left只能往右移动,指针right只能往左移动,然后在移动的过程中,去更新变量leftMax和rightMax的值。

在开始阶段left=0,right=n-1,leftMax=0,rightMax=0。然后**height[left]height[right]**进行比较。这时会出现两种情况:

1.如果此时height[left]<height[right],则leftMax=max(leftMax,height[left])=height[left]<height[right]=rightMax,所以下标left处能接的雨水量为leftMax-height[left],然后left往右移动一位,此时left=left+1=1

2.如果此时height[left]>=height[right],则leftMax=max(leftMax,height[left])=height[left]>=height[right]=rightMax,所以下标right处能接的雨水量位rightMax-height[right],然后right往左移动一位,此时right=right-1=n-2

然后循环执行1,2步,直到left和right相遇为止。

image-20210923150643236

image-20210923150731710

image-20210923150755403

下面我们来总结一下,在指针leftright没有相遇时,我们执行如下操作。

1.使用height[left]height[right]来更新leftMaxrightMax

2.如果height[left]<height[right],则必有leftMax<rightMax。下标left处能接的雨水量为leftMax-height[left],然后left往右移动一位。

3.如果height[left]>=height[right],则必有leftMax>=rightMax。下标right处能接的雨水量为rightMax-height[right],然后right往左移动一位。


下面我们来看一下代码的实现。

def result(height):
    if not height:
        return

    n=len(height)

    left=0
    leftMax=0
    right=n-1
    rightMax=0

    ans = 0
    while left < right:
        leftMax=max(leftMax,height[left])
        rightMax=max(rightMax,height[right])

        if height[left] < height[right]:
            ans=ans+leftMax-height[left]
            left=left+1
        else:
            ans=ans+rightMax-height[right]
            right=right-1

    return ans

height=[4,2,0,3,2,5]
print(result(height))

我们通过分析代码可以很容易的知道,这个算法的时间复杂度是O(n),空间复杂度为O(1)。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。