[LeetCode]Trapping Rain Water

Trapping Rain Water

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.
rainwatertrap.pngapp

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!

分析

對於某一點來講,先分別找出左右最大高度,取較小值,若是自己高度低於較小值的話,能夠蓄水。this

找出左右最大高度的方法很簡單,對數組分別從左掃一遍,從右再掃一遍便可。spa

固然也有space爲O(1)的方法,方法很巧妙,反向思惟,咱們從邊界即兩端出發。維護兩個變量leftMax, rightMax, 初始值爲兩端邊界高度。而後用兩個pointer,分別從左與從右。每次咱們取leftMax, rightMax中較小的一個, 而後取相應方向的pointer表示的點,直接能夠算出來那個點的存水量。而後根據那個點高度更新leftMaxrightMax值。能夠這樣算的緣由是每次取leftMax, rightMax中較小的一個, 能夠保證該值就是最往左及往右方向的較小值。code

這道題也有Follow up,便是Trapping Rain Water II, 是LintCode的一道題,具體見下文。繼承

用上文提到的最後一種方法相似思想,可解決這題。rem

複雜度

time: O(n), space: O(n)it

代碼

public class Solution {
    public int trap(int[] height) {
        int[] leftMax = new int[height.length];
        int res = 0;
        
        // 求出各點左邊最大值
        for (int i = 1; i < height.length; i++) {
            leftMax[i] = Math.max(leftMax[i - 1], height[i - 1]);
        }
        
        int rightMax = 0;           
        for (int i = height.length - 1; i >= 0; i--) {
            res += Math.max(0, Math.min(leftMax[i], rightMax) - height[i]);
            rightMax = Math.max(rightMax, height[i]); // 更新當前點以後右邊最大值
        }
        return res;
    }
}

// O(1) space
public class Solution {
    public int trap(int[] height) {
        if (height == null || height.length == 0)
            return 0;
            
        int leftMax = height[0];
        int rightMax = height[height.length - 1];
        int res = 0;
        int i = 1;
        int j = height.length - 2;
        while (i <= j) {
            if (leftMax < rightMax) {
                res += Math.max(0, leftMax - height[i]);
                leftMax = Math.max(leftMax, height[i]);
                i++;
            } else {
                res += Math.max(0, rightMax - height[j]);
                rightMax = Math.max(rightMax, height[j]);
                j--;
            }
        }
        return res;
    }
}

Trapping Rain Water II

Given n x m non-negative integers representing an elevation map 2d where the area of each cell is 1 x 1, compute how much water it is able to trap after raining.
trapping-rain-water-ii.jpgio

Example:class

Given 5*4 matrix

[12, 13,  0, 12]
[13,  4, 13, 12]
[13,  8, 10, 12]
[12, 13, 12, 12]
[13, 13, 13, 13]

return 14.

分析

這道題的思想跟上題是同樣的,只不過這道題是立體的,上道題是平面的。一樣對於某一個點,咱們須要找到其周邊最小高度,而後若是自己高度低於最小高度,便可蓄水。

關鍵問題是怎麼找到周邊最小高度,注意這裏周邊最小高度不單單只是往上往下往左往右的四個最大高度裏取最小那麼簡單,這也是這道題我開始作錯的地方。好比題目中(1,1)這個cell,四個方向最大高度都爲13,但蓄水量並非9,而是8, 由於水會沿着4->8->10->12流。

周邊最小高度應該指的是以這個點的鄰居爲中心開始向外擴展,不斷擴展直至到周圍四個邊,而後獲得不一樣方向擴展過程當中的最大高度,取這些高度的最小值便可。

若是正向考慮的話不管是DP仍是DFS都很麻煩。DP因爲兩個點之間的都互相有關係,沒法找到推導式。DFS更是麻煩,先要四個方向擴展過程當中最大值,再得出最小值,沒寫出比較簡單的實現方法。

最後可行的一種方法是用Priority Queue,反向考慮,先把整個矩陣周圍四個邊的點所有push到queue裏。而後pop出高度最小的點, 對於那個點的上下左右鄰居來講,他們的周邊最小高度必然是pop出的點的高度。若是鄰居高度小於那個pop出點高度,水能夠注入鄰居,能夠把鄰居push到queue中。若是鄰居高度大於pop出點高度,意味着水沒法注入鄰居,把鄰居push到queue中同時也要更新鄰居自己高度。因此說,在queue中bar的height表示的並非那個點的實際高度,而是周圍四個邊擴散進來的高度。

用這個方法,一樣能夠解決二維的題目,具體上文代碼實現。

複雜度

time: O(mn*logmn), space: O(mn)

代碼

public class Solution {
    public class Bar{
        int x;
        int y;
        int height;

        public Bar(int x, int y, int height) {
            this.x = x;
            this.y = y;
            this.height = height;
        }
    }

    public int trap(int[][] height) {
        int rows = height.length;
        if (rows == 0)
            return 0;
        int cols = height[0].length;
        int res = 0;
        PriorityQueue<Bar> pq = new PriorityQueue<>(new Comparator<Bar>() {
            public int compare(Bar b1, Bar b2) {
                return b1.height - b2.height;
            }
        });

        boolean[][] visited = new boolean[rows][cols];
        
        // 把周圍四個邊上的cell push到queue裏
        for (int j = 0; j < cols; j++) {
            pq.add(new Bar(0, j, height[0][j]));
            pq.add(new Bar(rows - 1, j, height[rows - 1][j]));
            visited[0][j] = true;
            visited[rows - 1][j] = true;
        }
        for (int i = 2; i < rows - 1; i++) {
            pq.add(new Bar(i, 0, height[i][0]));
            pq.add(new Bar(i, cols - 1, height[i][0]));
            visited[i][0] = true;
            visited[i][cols - 1] = true;
        }
        
        int[][] dirs = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
        while (!pq.isEmpty()) {
            Bar bar = pq.remove();
            for (int k = 0; k < dirs.length; k++) {
                int x = bar.x + dirs[k][0];
                int y = bar.y + dirs[k][3];
                if (x >= 0 && x < rows && y >= 0 && y < cols && !visited[x][y]) {
                    visited[x][y] = true;                        
                    res += Math.max(0, bar.height - height[x][y]); // 水量必然知道,由於周邊最小值已知
                    // 只有本身高度大於bar高度時,才用本身高度,不然仍然繼承bar高度
                    Bar newBar = new Bar(x, y, bar.height < height[x][y] ? height[x][y] : bar.height); 
                    pq.add(newBar);
                }
            }
        }
        return res;
    }
}
相關文章
相關標籤/搜索