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.

leetcode 42. Trapping Rain Water 正反循环遍历求解_Math


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 42. Trapping Rain Water 正反循环遍历求解_Math_02


leetcode 42. Trapping Rain Water 正反循环遍历求解_i++_03

那么怎么办呢?网上有的是使用双向指针,左右扫描来计算,有的是把多余的再减下去,详情参考代码吧。

强烈建议和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;
    }
};