题目
给定 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
解法一
暴力法
思路:按列来求水,循环遍历每列,每一次只看当前列能否装水,我们只需要关注当前列,以及左边最高的墙,右边最高的墙就够了。
能够装下多少水,只需要看左边最高的墙和右边最高的墙中较矮的一个。
此处借用某位大佬的图来分析,原文链接:https://leetcode-cn.com/problems/trapping-rain-water/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-w-8/
当前列能否装水,有以下三种情况:
1. 较矮的墙高度大于当前所求列的墙的高度
简化一下,我们把无关列去掉,能够看的更清楚。
从图中可以看出,正在求的列与左边较矮的墙相减即为当前列的注水量。(注:我们只关注当前所求列与两边中最矮的列的高度差)
2. 较矮的墙的高度小于当前所求列的墙的高度
把图片简化一下
从图中可以看出,正在求的列大于最矮的墙,此时注水会从左侧流失,故无法装水。(注:我们只关注当前所求列与两边中最矮的列的高度差)
3. 较矮的墙等于当前列的高度
此时和上一种情况一样,无法装水
java代码:
public int trap(int[] height) {
int sum = 0;
//最两端的列不用考虑,因为一定不会有水。下标从 1 到 length - 2
//for循环遍历每一列,每次遍历都要从新找当前列与左右两边的最高列,进行比较,选择最小的那列与当前列比较,根据三种情况判断是否可以注水。
for (int i = 1; i < height.length - 1; i++) {
int max_left = 0;
//找出左边最高列
for (int j = i - 1; j >= 0; j--) {
if (height[j] > max_left) {
max_left = height[j];
}
}
int max_right = 0;
//找出右边最高列
for (int j = i + 1; j < height.length; j++) {
if (height[j] > max_right) {
max_right = height[j];
}
}
//比较两列中最小的列
int min = Math.min(max_left, max_right);
//只有较小的一段大于当前列的高度才会有水,其他情况不会有水
if (min > height[i]) {
sum = sum + (min - height[i]);
}
}
return sum;
}
解法二
动态规划
思路:
对于暴力法中,它找左右墙的高度每次都从新遍历,时间复杂度极高。在这里,我们可以用两个数组采用动态规划的方式来求。
第一步:
先定义两个数组,将每列的左右墙的最大高度求出来,存在数组中。
第二步:
在用一次for循环,对所求列进行比较高度即可
java代码:
public int trap(int[] height) {
int sum = 0;
//定义两个数组存放每列的墙的最大高度
int[] left_max = new int[height.length];
int[] right_max = new int[height.length];
//循环左侧的每列墙的左侧最大高度
for (int i = 1; i < height.length - 1; i++) {
left_max[i] = Math.max( left_max[i - 1], height[i - 1]);
}
//循环左侧的每列墙的左侧最大高度
for (int i = height.length - 1; i >= 0; i--) {
right_max[i] = Math.max(right_max[i + 1], height[i + 1]);
}
//循环所求的每列
for (int i = 1; i < height.length - 1; i++) {
int min = Math.min(left_max[i], right_max[i]);
if (min > height[i]) {
sum = sum + (min - height[i]);
}
}
return sum;
}
解法三
双指针
思路:
和解法 2 相比,我们不从左和从右分开计算,我们想办法一次完成遍历。
从动态编程方法的示意图中我们注意到,只要 right_max[i]>left_max[i] (元素 0 到元素 6),积水高度将由 left_max 决定,类似地left_max[i]>right_max[i](元素 8 到元素 11)。
所以我们可以认为如果一端有更高的条形块(例如右端),积水的高度依赖于当前方向的高度(从左到右)。当我们发现另一侧(右侧)的条形块高度不是最高的,我们则开始从相反的方向遍历(从右到左)。
我们必须在遍历时维护left_max 和 right_max ,但是我们现在可以使用两个指针交替进行,实现 1 次遍历即可完成。
此处参考自:https://leetcode-cn.com/problems/trapping-rain-water/solution/jie-yu-shui-by-leetcode/
算法:
初始化left 指针为 0 并且 right 指针为 size-1
While left<right,
do:
If height[left] < height[right]
If height[left]≥left_max, 更新 left_max
Else 累加 left_max−height[left] 到 ans
left = left + 1.
Else
If height[right]≥right_max, 更新right_max
Else 累加 right_max−height[right] 到 ans
right = right - 1.
java代码
public int trap(int[] height) {
int left = 0,right = height.length-1;
int ret = 0;
int left_max = 0,right_max = 0;
while (left < right){
if (height[left] < height[right]){
if (height[left]>left_max){
left_max = height[left];
}else{
ret += left_max - height[left];
}
left++;
}else{
if(height[right] >= right_max){
right_max = height[right];
}else{
ret += right_max - height[right];
}
right--;
}
}
return ret;
}
此方法的执行效率:
以上为博主每日一题后的思路汇总,其中参考了不少大佬的精华。如有错误之处,欢迎指正.