题目描述:
Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining.
The above elevation map is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped. Thanks Marcos for contributing this image!
Example:
Input: [0,1,0,2,1,0,1,3,2,1,2,1] Output: 6
给定的数组代表柱子的高度,根据一组柱子高度,计算这组柱子中能存储水的值。
思路1:双指针
左右两边柱子都有高度时,才能存储水,所以我们在计算接雨水的量时需要从左右两边计算。
step1.找到能存储雨水的左边界left和右边界right。
对左边界left,是从数组头向数组尾看,第一次出现下降趋势的那个索引的位置。
对于右边界right,是从数组尾到数组头方向看,第一次出现下降趋势的那个索引的位置。
step2.记录左右边界的高度Hleft,Hright,显然雨水数由较低的边界决定。
①若Hleft<=Hright,
若满足left<right,说明左右边界没有重合,尝试让left+1,若left位置能够存储雨水,则更新结果值。
若left位置不能存储雨水,则说明left位置的高度大于Hleft,我们应该进入下一轮循环并更新Hleft的值。
②若Hleft>Hright,
若满足left<right,说明左右边界没有重合,尝试着令right-1。若right的位置能够存储雨水,则更新结果值。
若right的位置不能存储雨水,说明right位置的高度大于Hright,这时我们应该进入下一轮循环,更新Hright的值。
实现1:
class Solution {
public int trap(int[] height) {
int n=height.length;
int ret=0;
if(n==0||n==1){
return ret;
}
int left=0;
//找到第一个出现下降趋势的left
while(left<n-1&&height[left+1]>height[left]){
left++;
}
int right=n-1;
//找到第一个出现下降趋势的right
while(right>0&&height[right-1]>height[right]){
right--;
}
//左右指针不重复,不断比较更新左右最大高度和结果值
while(left<right){
int Hleft=height[left];
int Hright=height[right];
if(Hleft<=Hright){
//左边高度比右边低
while(left<right){
left++;
if(height[left]<Hleft){
ret+=Hleft-height[left];
}else{
break;
}
}
}else{
while(left<right){
right--;
if(height[right]<Hright){
ret+=Hright-height[right];
}else{
break;
}
}
}
}
return ret;
}
}
改进:
private int trap2(int[] height) {
int n=height.length;
int ret=0;
if(n==0||n==1){
return ret;
}
int left=0;
int right=n-1;
int Hleft=0;
int Hright=0;
while(left<right){
if(height[left]<=height[right]){
//从左边更新高度和结果值
Hleft=Math.max(height[left], Hleft);
ret+=Hleft-height[left];
left++;
}else{
//从右边更新高度和结果值
Hright=Math.max(height[right], Hright);
ret+=Hright-height[right];
right--;
}
}
return ret;
}
实现2:
private int trap(int[] nums) {
// TODO Auto-generated method stub
int n=nums.length;
int left=0;
int right=n-1;
int ret=0;
int maxLeft=0,maxRight=0;
while(left<=right){
//如果左边结果值较小,则从左边处理结果值
if(nums[left]<=nums[right]){
if(nums[left]>maxLeft){
maxLeft=nums[left];
}else{
ret+=maxLeft-nums[left];
}
left++;
}else{
//否则,从右边处理结果值
if(nums[right]>maxRight){
maxRight=nums[right];
}else{
ret+=maxRight-nums[right];
}
right--;
}
}
return ret;
}
思路2:堆栈
栈中存的是height数组的索引。
若栈为空或curr指向的height数组的元素小于等于栈顶元素,则一直入栈,因此栈顶元素索引对应的height
数组的值是整个栈中最小的。
一旦指针curr指向的height数组的值超过栈顶元素索引对应的值,则说明栈顶元素对应一个右边界。
因为栈顶元素是递减的,所以若存在一个比栈顶元素大的元素,则一定可以确定该横向区域的盛水量。
模拟的是一个
实现:
class Solution {
public int trap(int[] height) {
int n=height.length;
int ret=0;
if(n==0||n==1){
return ret;
}
int curr=0;
Stack<Integer> s=new Stack<>();
while(curr<n){
while(!s.isEmpty()&&height[curr]>height[s.peek()]){
//每当遇到比栈顶大时,在更新完面积之后,都需要将栈内清空,将最新的最大值放入栈顶。
int top=s.pop();
if(s.isEmpty()){
break;
}
int dis=curr-s.peek()-1;//宽是当前这个较大元素的索引减去栈顶元素的索引-1
//高度:因为是一层一层累积的,所以只需计算最高层的,我们用两边较大的元素的高度减去中间这个较小的元素的高度记为当前要往ret结果中增加的单元的高度
int currHeight=Math.min(height[curr], height[s.peek()])-height[top];
ret+=currHeight*dis;//累加完结果后,不断将新来的元素与剩下的栈顶元素比较大小更新结果值,直到栈为空
}
//保证栈内降序
s.push(curr);
curr++;
}
return ret;
}
}