Trapping Water II

Question quoted from lintcode

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.


img
Example:

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.

Idea

這題用母語都不容易把代碼講明白。
題目大意就是要向這些柱子組成的矩陣澆水,問一共能灌多少水。題目假設柱子之間沒有間隙。

我先講講我自己的思路吧。
思路說起來好像挺簡單的,就是從最外層開始澆水,層層往矩陣內部澆。
一圈柱子圍起來,它的可灌水高度,是由最低的一根柱子決定的。直觀上,你要把最外層的圈找到,得到可灌水高度(最低柱), 然後嘗試向圈內灌水,然後,往圈子內部找下一個更高的封閉圈,往上澆水,再找下一個封閉圈……

實際上代碼直接實現這個思路非常麻煩。把所有的封閉圈辨識出來就夠你喝一壺的。以下介紹另一種思路。非原創,代碼如有類同,實屬抄襲

代碼實現的思路

我們不考慮可灌水量,因爲每根柱子都不一樣,我們考慮灌水後的高度。這個高度對於每一個柱子圍成的圈來說, 每根內柱都是一樣,統一的,除非柱子超出了水面。

定義一個數據結構Cell, 裏面包含了柱子的位置,和柱子的灌水後高度

維護一個最小優先隊列,先把矩陣4條邊的柱子和他們的灌水後高度打包成Cell, 全部送進隊列,也就是首行尾行和首列尾列,對於這4條邊來說,每一根柱子的灌水後的高度應該是自身高度 + 0單位的水也就是柱子高度本身,因爲最外層邊柱不能夠被灌水。

然後不停執行隊列的出列操作。

推論1

這裏必須理解的是,由於最外圈已經首先訪問過了,並且全部排進了隊列,所以接下來出列的每一根柱子,還有他們的未訪問的相鄰柱子,都必然在不可能在最外圈以外。只能在最外圈或最外圈以內。

最小優先隊列第一次出列的,必然是邊柱最矮的一根。
檢查其前後左右而且"沒有訪問過"的相鄰柱子,嘗試向其灌水。如果鄰居比它的灌水後高度還矮,那就可以灌水。給鄰居柱子灌水以後,你可以把鄰居看成一條邊柱,它的高度爲“鄰居自身高度 + 灌上去的水”,也就是出列的柱子本身的高度,把這根虛擬的邊柱加進隊列,以便未來會對其鄰居進行灌水。如果鄰居比出列柱子的灌水後高度還要高,那這根柱子已經浮出水面了,把鄰居當作邊柱,直接加進隊列。

所以這裏對於每一根柱子的遍歷順序是這樣的:首先是最外包圍的圈,然後逐漸向內蔓延(前後左右的鄰居柱子)直到把整個矩陣都訪問一遍。

推論2

由於永遠是最矮的柱子先出列,所以可以確定的是,遍歷過程中,圍圈的最低柱子和它的鄰居總是會被最先灌水。

這個灌水的順序和現實物理是相悖的。這裏是理解代碼最困難的地方,一點也不直觀。

Code


class Cell {
    public final int rowNum; 
    public final int colNum;

    // 注意,這裏表示灌水後的高度! 如果這個位置不能被灌水,就會和柱子高度一樣,相當於加了0個單位的水
    public final int waterLevel; 

    Cell(int rowNum, int colNum, int level) {
        this.rowNum = rowNum;
        this.colNum = colNum;
        this.waterLevel = level;
    }
}

public class Solution {
    /*
     * @param heights: a matrix of integers
     * @return: an integer
     */
    public int trapRainWater(int[][] heights) {
        // write your code here
        if (heights.length == 0) return 0;
        
        // 創建一個最小優先隊列,語法挺麻煩的
        PriorityQueue<Cell> boundaryCells = new PriorityQueue<Cell>((c1, c2) -> {
            if (c1.waterLevel == c2.waterLevel) return 0;
            if (c1.waterLevel > c2.waterLevel) return 1;
            return -1;
        });

        int rowLen = heights.length;
        int colLen = heights[0].length;

        // initialize the variable
        boolean[][] visited = new boolean[rowLen][colLen];
        for (int i = 0; i < rowLen; i++) {
            for (int j = 0; j < colLen; j++) {
                visited[i][j] = false;
            }
        }

        // 把首行,尾行,首列,尾列的柱子都加進隊列
        int firstRow = 0;
        int firstColumn = 0;
        int lastRow = rowLen - 1;
        int lastColumn = colLen - 1;

        // first column and last column 首列尾列
        for(int row_i = 0; row_i < rowLen; row_i++) {
            boundaryCells.add(new Cell(row_i, firstColumn, heights[row_i][firstColumn]));
            boundaryCells.add(new Cell(row_i, lastColumn, heights[row_i][lastColumn]));
            visited[row_i][firstColumn] = true;
            visited[row_i][lastColumn] = true;
        }

        // first row and last row 首行尾行
        for(int column_i = 1; column_i < colLen - 1; column_i++) {
            boundaryCells.add(new Cell(firstRow, column_i, heights[firstRow][column_i]));
            boundaryCells.add(new Cell(lastRow, column_i, heights[lastRow][column_i]));
            visited[firstRow][column_i] = true;
            visited[lastRow][column_i] = true;
        }

        int water = 0;
        int[][] moves = { {1, 0}, {-1, 0}, {0, 1}, {0, -1}, };
        int row = 0; int col = 1;
        while(!boundaryCells.isEmpty()) {
            // start from the cell with least waterLevel 出列的順序一定是最矮的先出
            Cell current = boundaryCells.poll();

            // visit its 4 neighbors
            for(int[] move : moves) {
                int rowPos = current.rowNum + move[row];
                int colPos = current.colNum + move[col];

                // make sure the neighbor is valid index
                // and is never visited before
                boolean valid = (0 <= rowPos && rowPos < rowLen) &&
                        (0 <= colPos && colPos < colLen) &&
                        visited[rowPos][colPos] == false;

                if (valid) {
                    visited[rowPos][colPos] = true;
                    if (current.waterLevel > heights[rowPos][colPos]) {
                        water += current.waterLevel - heights[rowPos][colPos];
                        // 這是一根虛擬的邊柱,其高度 = 真實柱子高度 + 灌上的水, 也就是和當前柱子的灌水後高度一樣的
                        boundaryCells.add(new Cell(rowPos, colPos, current.waterLevel));
                    } else {
                        // 這根柱子浮出水面了,有可能成爲下一個"更高的內圈"的邊柱,這根柱子越高,就越排到後面才出列。
                        boundaryCells.add(new Cell(rowPos, colPos, heights[rowPos][colPos]));
                    }
                }
            }
        }
        return water;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355

推荐阅读更多精彩内容