当青训营遇上码上掘金
题目: 攒青豆
现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
以下为上图例子的解析:
输入:height = [5,0,2,1,4,0,1,0,3]
输出:17
解析:上面是由数组 [5,0,2,1,4,0,1,0,3] 表示的柱子高度,在这种情况下,可以接 17 个单位的青豆。
这是一道经典的题目,与Leetcode上42. 接雨水 - 力扣(LeetCode)一致。
题解
动态规划
思考一个问题:要接住青豆,应该满足什么条件?生活经验告诉我们:要接住青豆,应该同时保证青豆不会从右边流走也不会从左边流走。 首先,要保证青豆不会从左边流走,我们需要保证所有当前列的青豆高度(加上柱子本身的高度)小于等于左边柱子的最大高度。同理,要保证青豆不会从右边流走,我们需要保证所有当前列的青豆高度(加上柱子本身的高度)小于等于右边柱子的最大高度。这样我们就能得到两个方向的最大高度,最后计算每一列青豆的高度(不包含当前列柱子高度)只需选两最大值中较小的值与柱子高度做差即可。
我们使用leftMax和rightMax两个数组表示两个方向下青豆加上柱子到达的最大高度。明显有leftMax[0]=height[0],rightMax[n-1]=height[n-1]。接着要计算leftMax数组,我们从左往右遍历,下标i处的最大高度应该为leftMax[i-1]与height[i]的较大者。同理,要计算rightMax,我们只需要从右往左遍历,下标j处的最大高度应该为rightMax[j+1]与height[j]的较大者。
- 当 时,有leftMax[i]=max(leftMax[i−1],height[i]);
- 当 时,有rightMax[i]=max(rightMax[i+1],height[i])
下面用golang实现:
func trap(height []int) (ans int) {
n := len(height)
if n == 0 {
return
}
leftMax := make([]int, n)
leftMax[0] = height[0]
for i := 1; i < n; i++ {
leftMax[i] = max(leftMax[i-1], height[i])
}
rightMax := make([]int, n)
rightMax[n-1] = height[n-1]
for i := n - 2; i >= 0; i-- {
rightMax[i] = max(rightMax[i+1], height[i])
}
for i, h := range height {
ans += min(leftMax[i], rightMax[i]) - h
}
return
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
复杂度分析:
- 时间复杂度:O(n)。其中n为height数组的长度。计算数组leftMax 和 rightMax,leftMax和rightMax的元素值各需要遍历 一次,计算能接的雨水总量还需要遍历一次。
- 空间复杂度:O(n)。其中n为height数组的长度。算法需要额外使用leftMax和rightMax两个数组空间。
双指针
注意到下标 i 处能接的雨水量由leftMax[i]和rightMax[i]中的最小值决定。我们可以用left和right指针表示当前可以确定最大高度的位置。leftMax和rightMax表示当前已遍历的左边区域和右边区域的最大高度。有这么两种情况:
- 如果leftMax < rightMax ,则left指针的位置的最终最大高度可以确定,即为当前的leftMax,所以有当前位置left能攒的青豆数为
- 如果leftMax >= rightMax ,则right指针的位置的最终最大高度可以确定,即为当前的rightMax,所以有当前位置right能攒的青豆数为
下面用golang实现:
func trap(height []int) (ans int) {
left, right := 0, len(height)-1
leftMax, rightMax := 0, 0
for left < right {
leftMax = max(leftMax, height[left])
rightMax = max(rightMax, height[right])
if height[left] < height[right] {
ans += leftMax - height[left]
left++
} else {
ans += rightMax - height[right]
right--
}
}
return
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
复杂度分析:
- 时间复杂度:O(n)。其中n为height数组的长度。只需要遍历一次height数组。
- 空间复杂度:O(1)。只使用了两个指针和两个记录值。