LeetCode42-接雨水-圖解-四種解法-DP/雙指針/單調棧/暴力-Java

1 題目

題目鏈接:

https://leetcode-cn.com/problems/trapping-rain-water/ 

接雨水問題在leetcode中是“困難”,但同時也是面試中常遇到的問題。

1.1 題目描述:

給定 n 個非負整數表示每個寬度爲 1 的柱子的高度圖,計算按此排列的柱子,下雨之後能接多少雨水。

上面是由數組 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度圖,在這種情況下,可以接 6 個單位的雨水(藍色部分表示雨水)。 

示例:

輸入: [0,1,0,2,1,0,1,3,2,1,2,1]
輸出: 6

1.2 題目分析:

        這道題很想我們常說的“木桶效應”,說白了就是一個柱子能接多少水,取決於它兩邊“較短的板”,另外一個前提條件就是,兩邊的柱子高度都要比所要裝水的柱子的高度要高,否則肯定是無法裝水的。

        有了這個認識之後,再取解這道題目就不難了。

        如圖,我們要計算柱子i的接水容量的時候,能否接水取決於兩邊最高(left_max, right_max)的高度,但是接水的上限則取決於left_max和right_max中較小的一方(即上邊所說的“短板”)。

 

2 題解

前三種解法都是(如上圖)縱向考慮每個柱子能裝多少水。

第4種解法從橫向考慮問題,具體解釋見下文。

2.1 暴力

暴力解法比較好理解,但是時間複雜度O(n^2).

    // 42. 接雨水
    // 暴力
    public int trap(int[] height) {
        int len = height.length;
        if(len<=1) return 0;
        int res = 0;
        for (int i = 0; i < len; i++) {
            int l_max = height[0], r_max = height[len-1];
            for (int j = 0; j <= i; j++) {
                l_max = Math.max(l_max,height[j]);
            }
            for(int j=i; j<len;j++){
                r_max = Math.max(r_max,height[j]);
            }
            // System.out.println(l_max+" "+r_max);
            res += (Math.min(l_max,r_max)-height[i]);
        }
        return res;
    }

2.2 動態規劃

        動態規劃,可理解爲“用空間換時間”,也就是說,空間複雜度增大,但是時間複雜度降低。(另外避免一些重複計算)

        上文暴力解法,在內層for去尋找左右max的時候,其實是有重複計算的,而利用額外的空間去存儲前一個狀態的左右max則可以避免這些重複計算。

        即:創建兩個數組(l_max,r_max)去存儲,l_max[i]和r_max[i]在index=i的時候,其左右各自最高的高度。

        這種解法,時間空間複雜度都是O(N).

    public int trap(int[] height) {
        int len = height.length;
        if(len<=1) return 0;
        int res = 0;
        int[] l_max = new int[len];
        int[] r_max = new int[len];
        l_max[0] = height[0];
        r_max[len-1] = height[len-1];
        for (int i = 1; i < len; i++) {
            l_max[i] = Math.max(height[i],l_max[i-1]);
        }
        for(int i = len-2; i>=0;i--){
            r_max[i] = Math.max(height[i],r_max[i+1]);
        }
        for (int i = 0; i < len; i++) {
            res += Math.min(l_max[i],r_max[i])-height[i];
        }
        return res;
    }

2.3 雙指針(前後指針)

雙指針和前邊兩種方法類似,但是又有一些細微的區別,看下圖:

參考: https://www.cnblogs.com/labuladong/p/12320514.html 

        如計算index=left處的接水容量,雙指針法只在意l_max是(l_max,r_max)二者中較小的一方,但是並不關係,r_max是不是left右邊最高的(r_max只是right右側最高的。)

        時間複雜度O(N),空間複雜度O(1).

    // 雙指針(左右指針)
    public int trap3(int[] height) {
        int len = height.length;
        if(len<=1) return 0;
        int res = 0;
        int l_max = height[0], r_max = height[len-1];
        int left = 0, right = len-1;
        while (left<right){
            l_max = Math.max(height[left],l_max);
            r_max = Math.max(height[right],r_max);
            if(l_max<r_max){
                res += Math.min(l_max,r_max)-height[left];
                left++;
            } else{
                res += Math.min(l_max,r_max)-height[right];
                right--;
            }
        }
        return res;
    }

2.4 單調棧

單調棧的思路跟上述三種方法則完全不同,它從橫向去考慮問題,如圖:

        類似於leetcode84-柱狀圖中的最大矩形,不過這裏使用單調(遞減)棧,即:height[i]比stack的height[peek]小的時候才入棧,否則出棧,出棧則計算接水量。

        同樣類似“最大矩形問題”,兩邊加入兩個哨兵(兩個0)

        詳細圖解:

        這裏用單調遞減棧,與單調第增棧,有所不同的是,有元素出棧後,stack可能爲空,則無法取棧頂元素peek,這裏需要“仍把它pop,但是不計算面積”,因爲新的元素比pop的元素大,所以小的元素就沒有用了,(從座標系來看)我們只關心左邊最高的柱子,而且,如果不pop它,加入新元素後,棧內順序就不是遞減了。

        入棧、出棧的棧圖可以自己動手畫一下,更有助於理解,這裏只講一下流程,棧圖省略。

代碼:

    // 單調“遞減”棧
    public int trap4(int[] height) {
        int len = height.length;
        if(len<=1) return 0;
        int res = 0;
        Deque<Integer> stack = new ArrayDeque<>();
        int[] new_height = new int[len+2];
        System.arraycopy(height,0,new_height,1,len);
        for (int i = 0; i < len+2; i++) {
            while(!stack.isEmpty() && new_height[i]>new_height[stack.peek()]){
                int cur = stack.pop();
                if(stack.isEmpty())
                    break;
                int pk = stack.peek();
                int area = (i - pk - 1) * (Math.min(new_height[pk], new_height[i]) - new_height[cur]);
                res += area;
                // System.out.println(pk);
            }
            stack.push(i);
        }
        return res;
    }

單調棧解法的時間複雜度O(N),空間複雜度也是O(N). 其實看起來單調棧方法還沒有“動態規劃”和“雙指針”優越,這裏主要是用於方法學習。

 

以上是我的個人理解,如有錯誤,歡迎批評指正!謝謝!

 

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