Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
近期在看新的工作机会,跟同事一起刷题,一同事让我看看接雨水的题目。这是来自 leetcode 题库的第 42 题。描述如下:
题目描述
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例 1:
输入:height = [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 个单位的雨水(蓝色部分表示雨水)。
示例 2:
输入:height = [4,2,0,3,2,5] 输出:9
思路分析
讲真,做这种题我一般第一感觉是去暴力破解。但是看来官方题解后,发现解法还是有很多的,而且效率还很好。比较好的解法是双指针解法。但直接上双指针会难以理解。
所以,还是先理解一下动态规划解法吧。
先明确一下变量概念,从左往右,用 leftMax[i] 表示索引为 0 ~ i 之间的最大元素值;类似的,从右往左,用 rightMax[i] 表示 i ~ len(height)-1 之间的元素最大值。
通过两次循环遍历,得到每个下标所对应的 leftMax 和 rightMax,并以相同的下标为 key,存储到数组中备用,记为 leftMaxArr 和 rightMaxArr。
然后从左往右再遍历一次,分别比较 leftMaxArr 和 rightMaxArr 在下标 i 处的值,取最小的那个,减去当前 i 处的 height[i] 值,就是下标 i 处所能存储的单位水量。将其累加,即可得到总的蓄水量。
AC 代码
// trapDp 动态规划解法
func trapDp(height []int) int {
if len(height) < 1 {
return 0
}
length := len(height)
// 两个数组存储两边的 side max
leftMaxArr, rightMaxArr := make([]int, length), make([]int, length)
leftMax, rightMax := height[0], height[length-1]
for i, val := range height {
if val > leftMax {
leftMax = val
}
leftMaxArr[i] = leftMax
}
for j := length - 1; j >= 0; j-- {
if height[j] > rightMax {
rightMax = height[j]
}
rightMaxArr[j] = rightMax
}
total := 0
// 从左往右,leftMax[i]
for i := 0; i < length; i++ {
total += Min(leftMaxArr[i], rightMaxArr[i]) - height[i]
}
return total
}
func TestTrap1(t *testing.T) {
heightArr := []int{0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1}
res := trap(heightArr)
expected := 6
if res != expected {
t.Error("res is error[1]")
return
}
t.Log("--ok, end--")
}
总结
动态规划理解起来虽然也不容易,但按照思路多走几次,逻辑上还是可理解的。双指针解法是在此基础上更进一步,只用两个变量存储 leftMax 和 rightMax,空间复杂度上提升为常数级,值得学习!