【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;
    }
};

三、總結

哇這題好難,理解起來都難,我要是自己把這思路寫出來,不知道要多少行代碼。

 

 

 

 

 

 

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