题目描述
给定 n 个非负整数表示每个宽度为 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
思路分析
看题的第一反应就是无从下手,那我们就从局部入手。
备忘录
看第i个位置,可以发现能装多少水取决于第i位置的左边和右边最高柱子有关,即 i 位置的水柱高度 = min(l_max, r_max)。
思路就清晰了,即求每一个位置上 min(l_max, r_max)。可以立马发现有重叠子问题,就用备忘录解决。设置两个数组,lMax 记录 i 左边最高柱子高度,rMax 记录 i 右边最高柱子高度。先提前算好,在计算sum时直接用,这样时间复杂度是O(N),空间复杂度是O(N)。
双指针
看了解析,发现更巧妙的解法是使用双指针,相当于从两边往中间算雨水的块数。l_max 记录 0-left 的最高柱子高度,r_max 记录 right-n 的最高柱子高度。l_max 小就算 left 位置的雨水块数,再把 left++,右边同理,直到两指针相遇。
3D接雨水
3D版本就是木桶效应。维护一个优先队列,把最外围一圈入队。每次把最小的元素弹出,和其四个方向比较,若弹出的高度比周围大就是能灌水弹出后要把这个元素放进去继续作为边界;小就是不能灌水,继续队列弹出元素。
再用个visited数组用来表示是否遍历过。
代码实现
public class Trap_42 {
/**
* 备忘录解法
* @param height
* @return
*/
public static int trap(int[] height) {
if (height == null || height.length == 0) {
return 0;
}
int n = height.length;
int sum = 0;
int[] lMax = new int[n];
int[] rMax = new int[n];
lMax[0] = height[0];
rMax[n - 1] = height[n - 1];
for (int i = 1; i < n; i++) {
lMax[i] = Math.max(height[i], lMax[i - 1]);
}
for (int i = n - 2; i >= 0; i--) {
rMax[i] = Math.max(height[i], rMax[i + 1]);
}
for (int i = 1; i < n; i++) {
int dis = Math.min(lMax[i], rMax[i]) - height[i];
sum = dis > 0 ? sum + dis : sum;
}
return sum;
}
/**
* 双指针解法
* @param height
* @return
*/
public static int trap1(int[] height) {
if (height == null || height.length == 0) {
return 0;
}
int n = height.length;
int sum = 0;
int left = 0, right = n - 1;
int l_max = height[left], r_max = height[right];
while (left <= right) {
l_max = Math.max(l_max, height[left]);
r_max = Math.max(r_max, height[right]);
if (l_max < r_max) {
sum += l_max - height[left];
left++;
} else {
sum += r_max - height[right];
right--;
}
}
return sum;
}