接雨水

接雨水

https://leetcode-cn.com/problems/trapping-rain-water/

暴力法

一个格子 i 能接多少水,取决于它左右两边最高的柱子的最小值,减去它自己的高度 height[i]。可以对于每个格子 i 都寻找它左右两边最高柱子,复杂度为 O(n^2)。

动态规划

可以利用动态规划预先记录每个格子左边以及右边的最高长度。

具体地,用两个数组记录,leftMax[i] 表示第 [0..i] 的最高柱子高度(包括了 i 自己),base case 就是 leftMax[0] = height[0]。右边的最高高度同理,算出这两个数组后,计算结果仅需 O(n) 复杂度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func trap(height []int) int {
n := len(height)
leftMax := make([]int, n)
rightMax := make([]int, n)

leftMax[0] = height[0]
rightMax[n-1] = height[n-1]

for i := 1; i < n; i++ {
leftMax[i] = max(height[i], leftMax[i-1])
}
for j := n-2; j >= 0; j-- {
rightMax[j] = max(height[j], rightMax[j+1])
}

res := 0
for i := 0; i < n; i++ {
res += min(leftMax[i], rightMax[i]) - height[i]
}

return res
}

双指针

利用两个指针 leftright 从左右两边向中间靠近并计算 height[left]height[right] 能装的雨水,同时更新左侧和右侧的最高值。

由于已经判断了 leftMax < rightMax,因此直接取其中更小者即可,而不用关心另一侧是不是没找到最高的柱子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func trap(height []int) int {
n := len(height)

leftMax, rightMax := 0, 0
left, right := 0, n-1
res := 0

for left < right {
leftMax = max(leftMax, height[left])
rightMax = max(rightMax, height[right])

if leftMax < rightMax {
res += leftMax - height[left]
left++
} else {
res += rightMax - height[right]
right--
}
}

return res
}

盛最多水的容器

https://leetcode-cn.com/problems/container-with-most-water/

类似的道理,利用双指针解决,用 leftright 两个指针从两端向中心收缩,一边收缩一边计算 [left, right] 之间的矩形面积,取最大的面积值即是答案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func maxArea(height []int) int {
n := len(height)
var leftMax, rightMax int
left, right := 0, n-1

res := 0
for left < right {
leftMax = max(leftMax, height[left])
rightMax = max(rightMax, height[right])

var curr int
if leftMax < rightMax {
curr = leftMax * (right - left)
left++
} else {
curr = rightMax * (right - left)
right--
}

if curr > res {
res = curr
}
}

return res
}