描述
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
分析
解题思路有两类,一类是计算每一个柱体承接的雨水然后想加,解法有双指针,数组(左右两侧最大高度);一类是计算一段区间水平方向的面积,解法有单调栈。
暴力解法每次都有遍历左右两边寻找最大值,所有时间复杂度是O(n^2)。
双指针时间复杂度和空间复杂度都最小,就是不好理解。
双指针思路: 设有两个变量left,right从左右两边开始遍历。高度更低的向里面缩,同时计算是不是低于它这一侧的最大值,若是计算接水量;若不是,更新它这一侧的最大值。当left,right相遇跳出循环。
双指针的思路是一个主体承接的雨水体积大小取决于它左右两侧最高柱体较低的那个柱体与自己的差。双指针从左右两侧向里面逼近,可以计算双指针高低较低的一侧的柱体,因为它的左右两侧最高柱体较低的那个柱体已经知道了,所以可以计算,计算之后移动指针,继续比较左右指针哪个高哪个低。
数组保存左右两边最大值
ldp[i]表示从0到i的最大值,rdp[i]表示从i到arr.length - 1的最大值。
当拿到左右两边的最大值,取两者中的最小值,若大于arr[i],计算接水量。
“数组保存左右两边最大值”解法比“双指针”更容易理解。
import java.util.*;
public class Solution {
public long maxWater (int[] arr) {
//为了防止求和超出Int最大值,使用long记录最有两边最大值。
long[] ldp = new long[arr.length];
long[] rdp = new long[arr.length];
long lmax = 0, rmax = 0, sum = 0;
for(int i = 0; i < arr.length; i++){
lmax = Math.max(lmax,arr[i]);
ldp[i] = lmax;
}
for(int i = arr.length - 1; i >= 0; i--){
rmax = Math.max(rmax,arr[i]);
rdp[i] = rmax;
}
for(int i = 0; i < arr.length; i++){
if(Math.min(ldp[i],rdp[i]) > arr[i]){
sum += Math.min(ldp[i],rdp[i]) - arr[i];
}
}
return sum;
}
}
双指针解法
class Solution {
//一侧的最高值确定后,另一侧的高度比这一侧的最高值还要高,呢么这个位置的承接量就确定了。向里面缩的是较低的一侧。
//不会遇到
public int trap(int[] height) {
if(height.length < 2) return 0;
int left = 0, right = height.length - 1;
int lmax = 0, rmax = 0;
int sum = 0;
while(left < right){
if(height[left] < height[right]){
if(lmax > height[left]){
sum += lmax - height[left];
}else{
lmax = height[left];
}
left++;
}else{
if(height[right] < rmax){
sum += rmax - height[right];
}else{
rmax = height[right];
}
right--;
}
}
return sum;
}
}