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.
For example,
Given [0,1,0,2,1,0,1,3,2,1,2,1], return 6.
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!这道题的意思如上图,就是求积水的多少,这个属于典型的属于双指针求解的问题,和这道题有点类似 leetcode 11. Container With Most Water,可以放到一起学习。具体怎么做呢,如下?
因为我们是要求积水,相当于求落差空间,那么直接的想法就是对于数组从左向右遍历,使用左边的max值减去当前的值就是最后结果,但是这样的话,或多余的计算一部分,如下图:
那么怎么办呢?网上有的是使用双向指针,左右扫描来计算,有的是把多余的再减下去,详情参考代码吧。
强烈建议和leetcode 407. Trapping Rain Water II 寻找水+最短木桶现象 一起学习
代码如下:
public class Solution
{
/*
* 本方法和第二个方法都是利用双指针来计算空间吗,为什么设置左右指针呢?
* 因为仅仅按照左指针计算在计算右边的时候会计算多余的空间,
* 个人强烈建议手动画一下。
*
* http://www.cnblogs.com/zihaowang/p/5027379.html
*
* 开辟两个数组空间,逐个遍历数组,找出该位置左边的最大值与右边的最大值,
* 分别放到两个数组中。然后对整个数组进行遍历,位置装水后的值不能超过该位置
* 左右最高值中的最小数。该算法需三次遍历数组,但是时间复杂度为O(n);
* 空间需开辟两个数组空间,空间复杂度为O(n)。
* */
public int trapWithON(int[] height)
{
if(height==null || height.length<=1)
return 0;
int[] leftMax=new int[height.length];
int[] rightMax=new int[height.length];
int lmax=0,rmax=0;
//分别计算左边最高和右边最高
for(int i=0;i<height.length;i++)
{
leftMax[i]=lmax;
lmax=Math.max(lmax, height[i]);
}
for(int i=height.length-1;i>=0;i--)
{
rightMax[i]=rmax;
rmax=Math.max(rmax, height[i]);
}
//根据左边最高和右边最高选取最低者,然后计算相关空间
int sum=0;
for(int i=0;i<height.length;i++)
{
int miniHeight=Math.min(leftMax[i], rightMax[i]);
if(miniHeight>height[i])
sum+=(miniHeight-height[i]);
}
return sum;
}
/*
* http://www.cnblogs.com/zihaowang/p/5027379.html
*
* 设置两个指示变量,分别存放当前指向的两个位置。找出左位置的左边的最高值和右位置的右边的最高值。
* 对于两者中的最小值,表明当前位置加上水过后的值不超出该值,那么相减即可,反之,对另一个相减。
* 该算法只需要一次遍历数组,所以效率更高,时间复杂度为O(n);空间方面不需要开辟数组空间,
* 所以为常数空间。
* */
public int trapWithO1(int[] height)
{
if(height==null || height.length<=1)
return 0;
int left=0,right=height.length-1;
int leftMax=0,rightMax=0;
int sum=0;
while(left<right)
{
leftMax=Math.max(leftMax, height[left]);
rightMax=Math.max(rightMax, height[right]);
if(leftMax<rightMax)
{
sum+=(leftMax-height[left]);
left++;
}else
{
sum+=(rightMax-height[right]);
right--;
}
}
return sum;
}
/*
* http://blog.csdn.net/javays1/article/details/48651263
*
* 这个就是仅仅使用leftMax去计算得到的计算结果sum是多余的
* 所以还要去除多余部分,和前面的两个方法计算思想是一样的
*
* */
public int trap(int[] height)
{
if(height==null || height.length<=1)
return 0;
int index=0;
while(index<height.length && height[index]<=0)
index++;
int leftMax=0,sum=0;
for(int j=index;j<height.length;j++)
{
leftMax=Math.max(leftMax, height[j]);
sum+=(leftMax-height[j]);
}
int nowMax=0;
for(int j=height.length-1;j>=index;j--)
{
if(height[j]==leftMax)
break;
else
{
nowMax=Math.max(nowMax, height[j]);
sum-=(leftMax-nowMax);
}
}
return sum;
}
}
下面是C++的代码,就是双向循环遍历计算的问题
代码如下:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Solution
{
public:
int trap(vector<int>& height)
{
if (height.size() <= 1)
return 0;
int* leftMax = new int[height.size()];
int* rightMax = new int[height.size()];
int lmax = 0,rmax = 0;
for (int i = 0; i < height.size(); i++)
{
leftMax[i] = lmax;
lmax = max(lmax, height[i]);
}
for (int i = height.size()-1; i >=0 ; i--)
{
rightMax[i] = rmax;
rmax = max(rmax, height[i]);
}
int sum = 0;
for (int i = 0; i < height.size(); i++)
{
int mini = min(leftMax[i], rightMax[i]);
if (mini > height[i])
sum += (mini - height[i]);
}
delete[] leftMax;
delete[] rightMax;
return sum;
}
};