【LeetCode】42. Trapping Rain Water 储存雨水

一、概述

输入一个数组,数组元素表示墙高,现在下一场雨,输出墙之间的空隙能储存多少雨。

挺有意思的一道题,题目中有图片,所以理解题意不难。

但是我的想法爆时间了,就很难受。想半天想不出好方法,然后去看了答案,这尼玛真的有点难想,我看了好一会才理解。

二、分析

1、我的思路

我在理解了之后第一个想到的方法就是从下往上从左往右遍历每一个格子,然后从第一个不为0的数字数到最后一个不为0的数字,0的个数就是该层能储存的雨水的数量。每遍历完一层,该层所有不为0的元素全部减一。直到遍历完。时间复杂度为O(Hmax*n),也就是说,一旦有一堵墙特别高,我这算法的时间就会很长很长,因为每次高度都只减一,效率很低。代码如下:

class Solution {
public:
    int trap(vector<int>& height) {
        int n=height.size();
        int left=-1,right=-1;
        int flag;
        int result=0;
        do
        {
            flag=0;
            left=-1;
            right=-1;
           for(int i=0;i<n;i++)
           {
               if(height[i]!=0&&left==-1)
               {
                   left=i;
                   height[i]--;
               }
               else if(height[i]!=0&&right==-1)
               {
                   right=i;
                   result=result+right-left-1;
                   right=-1;
                   left=i;
                   height[i]--;
                   flag=1;
               }
           }
        }
        while(flag!=0);
        return result;
    }
};

2、较好的思路

在我的思路中,每次判断的是“每行储水量”,这一思路中则判断的是“每列储水量”,因此它的时间复杂度仅为O(n)。但是有点难理解。

每列能够储存多少水呢?它取决于三个值,其一,该列左侧墙的最高值;其二,该列右侧墙的最高值;其三,该列墙的高。

在什么情况下能够储存水呢?在两侧最高值均大于该列墙的高度时能储存水。

能储存多少水呢?储存水量=Min(左侧最高值,右侧最高值)- 该墙高度。

问题出现在这里:我们如何知道“左侧最高值”、“右侧最高值”的值呢?要想知道就得遍历,这太麻烦了。有没有好一点的方法?

有的。

对于从左往右遍历的情况,“左侧最高值”很好找到,用一个值维护比当前墙高的值即可;同理对于从右往左的情况,“右侧最高值”很好找。

现在难找的剩下了两个“从左往右”的“右侧最高值”和“从右往左”的“左侧最高值”。

再想一想,我们需要最高值么?例如,对于从左往右,我们已经知道了“左侧最高值”,我们如果还知道“右侧有一个值比左侧最高值高”,那么该列墙能够储存多少水是不是就可以知道呢?

是的,如果右侧存在一个比左侧最高的墙还高的墙,那么限制当前墙储水量的就是左侧最高墙了。

也就是说,我们经过分析,将问题弱化为“在从左往右”情况下,找出右侧的一个值,它比左侧最高值大;“在从右往左”情况下,找出左侧一个值,它比右侧最高值大。

这个容易找么?

比之前找最大值容易。

既然我们已经有了为“在从左往右”情况下,左侧的最高值;那能不能把它当做“在从右往左”情况下,比右侧最高值大的那个值呢?

如果这样做,在左侧最高值比右侧最高值大的时候可以,但是小了就不可以。当小的时候,我们就可以把右侧最高值当做我们在从左往右时要找的值,对左侧进行处理了啊。

这样,仅需要知道左右两侧的最大值,就可以解决问题了。

通过下图进行分析:

输入数组为“0,2,0,4,1,2,5,3,3,0,1,0”。

首先,左指针left指向a[0],右指针指向a[11]。两边都是0,两边最大值仍为0。左指针右移。如下图:

现在left指向2,right指向0。左侧最大值更新为2。right<left,right左移。如下图:

现在left指向2,right指向1。右侧最大值更新为1。right<left,right左移。如下图:

现在left指向2,right指向0。最大值均不更新。说明当前高度比两侧最大值都小(或相等)。可以储存水了。由于之前有right<left,所以右侧最大值小于左侧,因此储存水量由右侧最大值限制,存水量为右侧最大值-当前高度=1-0=1。right<left,right右移。如下图:

现在left指向2,right指向3。右侧最大值更新为3。right>left,left右移。如下图:

现在left指向0,right指向3。最大值均不更新。说明可以储存水。由于之前right>left,说明左侧最大值小于右侧,因此储存水量由左侧最大值限制,存水量为2-0=2。right>left,left右移。如下图:

现在left指向4,right指向3,左侧最大值更新为4。left>right,right左移。如下图:

现在left指向4,right指向3。无最大值更新,由于之前left>right,存水量由右侧最大值限制,存水量为3-3=0。right<left,right右移。如下图:

现在left指向4,right指向5,右侧最大值更新为5。left<right,left右移。如下图:

现在left指向1,right指向5。无最大值更新。存水量为4-1=3。left<right,left右移。如下图:

现在left指向2,right指向5,。无最大值更新。存水量为4-2=2。left<right,left右移。

left==right,循环退出。

总存水量为8。

从上面的流程可以看出,储存雨水的条件为左右最大值均不更新,其值的计算根据当前left与right对应值的大小决定:要是left<right,说明最大值上也是left<right。反之亦成立。

这一点可能有点难理解,观察上述流程可知,每当选出一个最大值,则之后都是对面的指针移动,直到对面指针的值大于本侧最大值。也就是说,在left和right指向的两个值中,一定一直有一个值是当前的整体的最大值,那么如果left<right,则说明right对应的是当前整体的最大值,就一定有左侧最大值小于右侧。

因此代码如下:

class Solution {
public:
    int trap(vector<int>& height) {
        int n=height.size();
        int left=0,right=n-1;
        int MaxLeft=0,MaxRight=0;
        int res=0;
        while(left<right)
        {
            if(height[left]<=height[right])
            {
                if(height[left]>MaxLeft)
                    MaxLeft=height[left];
                else
                    res+=MaxLeft-height[left];
                left++;
            }
            else
            {
                if(height[right]>MaxRight)
                    MaxRight=height[right];
                else
                    res+=MaxRight-height[right]; 
                right--;
            }   
        }
        return res;
    }
};

三、总结

哇这题好难,理解起来都难,我要是自己把这思路写出来,不知道要多少行代码。

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章