程序员面试金典 - 面试题 17.21. 直方图的水量

题目难度: 困难

原题链接

今天继续更新程序员面试金典系列, 大家在公众号 算法精选 里回复 面试金典 就能看到该系列当前连载的所有文章了, 记得关注哦~

题目描述

给定一个直方图(也称柱状图),假设有人从上面源源不断地倒水,最后直方图能存多少水量?直方图的宽度为 1。

上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的直方图,在这种情况下,可以接 6 个单位的水(蓝色部分表示水)。  感谢 Marcos 贡献此图。

示例:

  • 输入: [0,1,0,2,1,0,1,3,2,1,2,1]
  • 输出: 6

题目思考

  1. 如何将问题简化?

解决方案

思路
  • 这道题乍一看无从下手, 如何直接求出总的蓄水量呢?
  • 这里我们可以将题目进行一些简化, 就是单独求出宽度为 1 的每个柱子的蓄水量, 然后汇总起来, 这样就能得到总的蓄水量
  • 那如何求单个柱子的蓄水量呢? 最开始我们可能想到利用相邻的柱子, 求出它们中较低的高度, 然后计算与当前柱子的高度差作为蓄水量, 但这似乎还不够
  • 例如上例最中间的柱子 (…,1,0,1,…的 0), 它两侧柱子的高度都是 1, 最终却能存高度为 2 的水
  • 这是为什么呢? 重新观察例子, 我们可以发现, 最中间的柱子 0 的蓄水量不是只由它的邻居决定, 而是由它左右两侧的最高柱子所决定
  • 它左侧的最高柱子高度为 2, 右侧是 3
  • 根据木桶原理, 最短板决定了桶的高度, 所以柱子 0 所在的的高度就是 2
  • 那么它的蓄水量就是桶高度减去它自身的高度, 2-0 = 0
  • 以此类推, 其他柱子的蓄水量也可以通过同样方式计算得到, 最后累加起来即为最终结果
  • 有了上面的分析, 我们就很好实现代码了:
    • 首先维护一个长度为 n 的列表, 存储当前下标左侧的最高高度
    • 然后从左向右遍历, 维护当前左侧最高高度, 并更新到该列表中
    • 最后再从右向左遍历, 同样得到右侧最高高度
    • 这样如果当前柱子的高度小于其左右最高高度的较小值时, 就说明该柱子能够蓄水, 其高度差就是蓄水量
  • 下面代码有详细的注释, 方便大家理解
复杂度
  • 时间复杂度 O(N): 只需要顺序和逆序各遍历 1 次
  • 空间复杂度 O(N): 维护了长度为 N 的左侧最高高度列表
代码
class Solution:
    def trap(self, height: List[int]) -> int:
        # 维护当前柱子的左侧最高高度
        lmxs = []
        lmx = 0
        for h in height:
            # 正序遍历, 记录左侧最高高度
            lmxs.append(lmx)
            # 注意左侧最高高度不能是柱子本身, 所以先追加再更新
            lmx = max(lmx, h)
        rmx = 0
        res = 0
        for i in range(len(height))[::-1]:
            # 逆序遍历, 记录右侧最高高度
            # 注意这里无需列表, 直接更新当前最大值即可
            h = height[i] # 当前柱子高度
            bh = min(lmxs[i], rmx) # 桶高度
            if bh > h:
                # 桶高度大于当前柱子高度, 则其高度差就是蓄水量
                res += bh - h
            # 右侧最高高度同样不能是柱子本身
            rmx = max(rmx, h)
        return res

大家可以在下面这些地方找到我~😊

我的 GitHub

我的 Leetcode

我的 CSDN

我的知乎专栏

我的头条号

我的牛客网博客

我的公众号: 算法精选, 欢迎大家扫码关注~😊

算法精选 - 微信扫一扫关注我

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值