Leetcode算法——42、雨水容量

76 篇文章 1 订阅

给定n个非负整数,代表一个高程地图,每个整数表示一个挡板,挡板高度为整数值,挡板宽度均为1(即挡板都是紧挨的,之间没有缝隙)。

计算下雨之后,整体可以存储多少水。

示例:
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:高度图如下:
在这里插入图片描述

那么下雨时,两个柱子之间如果有低谷,则会存储雨水,如下:
在这里插入图片描述

有 A、B、C三个低谷可以存储雨水,雨水总面积为 1+4+1=6。

思路

1、分治法

寻找到最高的两个柱子,他们两个之间如果还有其他柱子,则这些柱子高度都不高于他们两个,肯定可以蓄水。

可以根据两根柱子之间的较低高度以及两根柱子的距离,计算出蓄水的面积。注意,两根柱子之间的低位柱子们,相当于水底的小山丘,占据了水的体积,需要减掉。

然后,这两个柱子,将整个地图分成了三部分,中间这一部分刚刚已经计算过,使用递归继续处理剩下的两个水池

最后,三个水池的蓄水面积相加即可。

每次递归需要找到高度最高的两根柱子,其算法复杂度为 O(n);计算两根柱子之间的蓄水面积,算法复杂度为 O(n);递归次数为 O(logn)。因此,整体算法复杂度为 O(nlogn)。

2、双指针法

维护左右两个指针,作用是从首尾两端出发向中间进行遍历。

维护左右两个历史最高纪录,分别指向已扫描过的柱子中的最高柱子,作用是与当前指针计算高度差,高度差即为这根柱子上面的水的体积。

具体步骤如下:

  • 比较两个指针的柱子高度,选择较低者,比如为左指针(以下步骤全部按照左指针为较低者处理,右指针同理)。
  • 比较左指针和左最高柱子:
    (1)如果左指针的高度<左最高柱子,则说明左指针这根柱子上面肯定有蓄水(因为左指针这根柱子的右边肯定至少有一个柱子,即当前右指针,可以挡着它的蓄水,这也是第一步要选择较低者的原因),蓄水的高度即为(左最高柱子高度-左指针高度)。
    (2)如果左指针的高度>=左最高柱子,则说明上面肯定没有蓄水,因为左边没有柱子可以挡着水。这时需要更新左最高柱子为当前左指针。
  • 左指针+1。
  • 重复上面三步,直至左右指针相遇,算法结束。

python实现

import matplotlib.pyplot as plt
def trap(height):
    """
    :type height: List[int]
    :rtype: int
    分治法
    """
    
    l = len(height)
    if l <= 2:
        return 0
    
    # 1、寻找两个最高的柱子
    max1_idx = 0 if height[0] > height[1] else 1 # 最大值的索引
    max2_idx = 1 - max1_idx # 第二大值的索引
    for i in range(2, l):
        h = height[i]
        if h > height[max1_idx]:
            max2_idx = max1_idx
            max1_idx = i
        elif h > height[max2_idx]:
            max2_idx = i
    
    # 2、计算两个柱子之间的水量
    # 获取较低的高度
    small_height = min(height[max1_idx], height[max2_idx])
    if small_height == 0: # 较低高度为0,说明全场只有一根柱子有高度,存不了水
        return 0
    left_idx = min(max1_idx, max2_idx)
    right_idx = max(max1_idx, max2_idx)
    # 先计算两根柱子之间的容量
    middle_vol = max((right_idx - left_idx - 1) * small_height, 0)
    # 再减去之间的其他柱子所占据的体积
    middle_vol -= sum(height[left_idx + 1 : right_idx])
    
    # 3、递归计算剩余部分的水量
    return middle_vol + trap(height[:left_idx+1]) + trap(height[right_idx:])

def trap2(height):
    """
    :type height: List[int]
    :rtype: int
    双指针法
    """
    left,right = 0, len(height)-1
    maxleft , maxright= 0,0
    ret = 0
    while left <= right:
        if height[left] <= height[right]: # 左边低于右边
            if height[left] < maxleft: # 左边当前高度小于左边最大高度
                ret += maxleft - height[left] # 高度查为当前柱子上面的水量
            else: # 左边当前高度大于左边最大高度
                maxleft = height[left] # 更新左边最大高度
            left += 1

        else: # 右边低于左边
            if height[right] < maxright:
                ret += maxright - height[right]
            else:
                maxright = height[right]
            right -= 1

    return ret
        
if '__main__' == __name__:
    height = [0,1,0,2,1,0,1,3,2,1,2,1]
    
    # 画出柱状图
    X = list(range(0, len(height)))
    Y = height
    plt.bar(X, Y, 1)
    plt.ylim(0, max(Y)+1) 
    for a,b in zip(X,Y):
        plt.text(a, b+0.05, '%.0f' % b, ha='center', va= 'bottom',fontsize=11) 
    plt.show()
    
    print(trap2(height))
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值