该问题可被抽象为: n 个非负整数表示每个宽度为 1 的柱子的高度图,计算下雨后它能够捕获多少雨水。
上面是由数组 [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
解析
首先,对于每个柱子,我们需要知道其左侧最高的柱子和右侧最高的柱子。因为在这两个柱子之间,这个柱子可以捕获的雨水量就是它和左右两个柱子的最小高度的差。
具体来说,我们可以维护两个数组 leftMax 和 rightMax,其中 leftMax[i] 表示第 i 个柱子左侧最高的柱子高度,rightMax[i] 表示第 i 个柱子右侧最高的柱子高度。我们可以从左到右遍历一遍数组,计算出 leftMax 数组,然后从右到左遍历一遍数组,计算出 rightMax 数组。然后,我们可以再次从左到右遍历一遍数组,计算出每个柱子可以捕获的雨水量。
具体实现可以用一个栈来辅助完成。我们从左到右遍历一遍数组,遇到一个新的柱子时,如果栈为空,或者当前柱子的高度小于等于栈顶柱子的高度,就把当前柱子入栈。否则,我们可以弹出栈顶的柱子,计算出这个柱子可以捕获的雨水量,然后继续比较当前柱子和新的栈顶柱子的高度。如果当前柱子的高度大于新的栈顶柱子的高度,就把新的栈顶柱子弹出,计算出这个柱子可以捕获的雨水量,重复这个过程直到当前柱子可以入栈。
实现
下面是使用 golang 实现的代码:
func trap(height []int) int {
n := len(height)
if n == 0 {
return 0
}
leftMax := make([]int, n)
rightMax := make([]int, n)
leftMax[0] = height[0]
for i := 1; i < n; i++ {
leftMax[i] = max(leftMax[i-1], height[i])
}
rightMax[n-1] = height[n-1]
for i
for i := n - 2; i >= 0; i-- {
rightMax[i] = max(rightMax[i+1], height[i])
}
ans := 0
stack := make([]int, 0)
for i := 0; i < n; i++ {
for len(stack) > 0 && height[i] > height[stack[len(stack)-1]] {
top := stack[len(stack)-1]
stack = stack[:len(stack)-1]
if len(stack) == 0 {
break
}
left := stack[len(stack)-1]
width := i - left - 1
h := min(height[left], height[i]) - height[top]
ans += width * h
}
stack = append(stack, i)
}
return ans
}
func max(a, b int) int { if a > b { return a } return b }
func min(a, b int) int { if a < b { return a } return b }
时间复杂度
该算法的时间复杂度为 O(n),其中 n 是数组的长度。我们需要遍历数组三次,每次遍历都需要 O(n) 的时间,而栈的操作也需要 O(n) 的时间。因此,总的时间复杂度为 O(n)。
空间复杂度
该算法的空间复杂度为 O(n),其中 n 是数组的长度。我们需要使用两个数组来记录每个柱子的左侧和右侧最高的柱子,因此需要 O(n) 的空间。另外,我们还需要使用一个栈来辅助计算,最坏情况下栈的长度也是 O(n),因此总的空间复杂度为 O(n)。