给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
上面是由数组 [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
想要接住雨水 数组要大于3 如果只有两个数组无法接住雨水
接住雨水的前提是左右两边的数组高度要大于中间数组形成凹槽 并且雨水的接住量是矮的数组来决定
解法1
新建两个数组 a一个存放左边最大值 b一个存放右边最大值
然后遍历原始数组h,由于木桶效应 承载水的量由短的一边决定 min = min(a[i],b[i])然后比较min和h[i]的大小如果min>h[i] 就累加min-h[i]的值
以上的做法 需要三次遍历
可以把第二次遍历寻找右边最大值和判断min的大小的第三次遍历合成一次遍历
代码如下
public static int getS (int [] height){
int sum =0;
int[] a = new int [height.length];
int[] b = new int [height.length];
int leftmax=0;
int rightmax = 0;
for (int i = 0; i < height.length; i++) {
a[i] = leftmax;
leftmax = Math.max(leftmax,height [i]);
}
for (int i = height.length - 1; i >=0; i--) {
a [i]=Math.min(rightmax,a [i]);
rightmax = Math.max(rightmax,height [i]);
if(a[i]>height [i]){
sum += a[i]-height [i];
}
}
return sum;
}
解法2
使用两个指针,一个在数组的最左边,另一个在数组的最右边 记录指针所在位置的左边最大值和右边最大值 如果左边最大值小于右边最大值就移动左边指针,并且使用左边最大值减去指针位置数组值做累加,反之移动右边指针,直到两个指针相遇
代码如下:
if(height == null || height.length <3){
return 0;
}
int sum = 0;
int leftmax =0;
int rightmax =0;
int i =0;
int j = height.length -1;
while (i < j){
leftmax = Math.max(leftmax,height [i]);
rightmax = Math.max(rightmax,height [j]);
if(leftmax<rightmax){
sum += leftmax - height [i];
i++;
}else {
sum += rightmax - height [j];
j--;
}
}
return sum;
解法3
使用单调栈来解决接雨水问题
使用一个单调递减栈, 正好可以使用单调递减的性质,当数组中的数据出现比栈顶的数大的时候,来判断是不是可以接住雨水
以[0,1,0,2,1,0,1,3,2,1,2,1]数组为例 下图记录了 单调栈的操作流程
代码如下:
public static int trap2(int[] height){
int res = 0;
int n = height.length;
Deque<Integer> stack = new ArrayDeque<> ();
int curIndex = 0;
while (curIndex < n){
//peek()查看堆栈顶部的对象,但不从堆栈中移除它
while (!stack.isEmpty() && height [curIndex]>height [stack.peek()]){
int top = stack.pop();
if(stack.isEmpty()){
break;
}
int h = Math.min(height[stack.peek()],height [curIndex])-height [top];
int dist = curIndex - stack.peek() - 1;
res += h*dist;
}
stack.push(curIndex++);
}
return res;
}