题目难度: 困难
今天继续更新程序员面试金典系列, 大家在公众号 算法精选 里回复 面试金典 就能看到该系列当前连载的所有文章了, 记得关注哦~
题目描述
给定一个直方图(也称柱状图),假设有人从上面源源不断地倒水,最后直方图能存多少水量?直方图的宽度为 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,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
大家可以在下面这些地方找到我~😊
我的公众号: 算法精选, 欢迎大家扫码关注~😊