Leetcode --- 島嶼問(wèn)題(DFS求解)

寫在前參考這里

類比二叉樹(shù)吩抓,網(wǎng)格進(jìn)行DFS的兩個(gè)要素:

  • 訪問(wèn)相鄰節(jié)點(diǎn):對(duì)于格子 (r, c) 來(lái)說(shuō)(r 和 c 分別代表行坐標(biāo)和列坐標(biāo))蒿偎,四個(gè)相鄰的格子分別是 (r-1, c)、(r+1, c)丸凭、(r, c-1)钙蒙、(r, c+1)

  • base case:超出網(wǎng)格的范圍茵瀑,即先污染后治理原則间驮,發(fā)現(xiàn)錯(cuò)誤撤出躬厌。

網(wǎng)格 DFS 遍歷的框架代碼:

void dfs(int[][] grid, int r, int c) {
    // 判斷 base case
    if (!inArea(grid, r, c)) {
        return;
    }
    // 如果這個(gè)格子不是島嶼,直接返回
    if (grid[r][c] != 1) {
        return;
    }
    grid[r][c] = 2; // 將格子標(biāo)記為「已遍歷過(guò)」,避免重復(fù)遍歷
    
    // 訪問(wèn)上竞帽、下扛施、左、右四個(gè)相鄰結(jié)點(diǎn)
    dfs(grid, r - 1, c);
    dfs(grid, r + 1, c);
    dfs(grid, r, c - 1);
    dfs(grid, r, c + 1);
}

// 判斷坐標(biāo) (r, c) 是否在網(wǎng)格中
boolean inArea(int[][] grid, int r, int c) {
    return 0 <= r && r < grid.length 
            && 0 <= c && c < grid[0].length;
}

1.島嶼數(shù)量(200 - 易)

題目描述:給你一個(gè)由 '1'(陸地)和 '0'(水)組成的的二維網(wǎng)格屹篓,請(qǐng)你計(jì)算網(wǎng)格中島嶼的數(shù)量疙渣。島嶼總是被水包圍,并且每座島嶼只能由水平方向和/或豎直方向上相鄰的陸地連接形成堆巧。

此外妄荔,你可以假設(shè)該網(wǎng)格的四條邊均被水包圍。

示例 :

輸入:grid = [
  ["1","1","0","0","0"],
  ["1","1","0","0","0"],
  ["0","0","1","0","0"],
  ["0","0","0","1","1"]
]
輸出:3

思路:DFS搜索谍肤,套模板啦租。

代碼實(shí)現(xiàn):

public int numIslands(char[][] grid) {
    if (grid == null || grid.length == 0 || (grid.length == 1 && grid[0].length == 0)) {
        return 0;
    }
    int M = grid.length, N = grid[0].length;
    int ans = 0;

    for (int i = 0; i < M; i++) {
        for (int j = 0; j < N; j++) {
            if (grid[i][j] == '1') {
                ans++;
                dfs(grid, i, j);
            }
        }
    }
    return ans;
}

private void dfs(char[][] grid, int r, int c) {
    if (!inArea(grid, r, c) || grid[r][c] != '1') return;
    grid[r][c] = '2'; // 標(biāo)記為已遍歷

    dfs(grid, r - 1, c);
    dfs(grid, r + 1, c);
    dfs(grid, r, c - 1);
    dfs(grid, r, c + 1);
}

private boolean inArea(char[][] grid, int r, int c) {
    return 0 <= r && r < grid.length && 0 <= c && c < grid[0].length;
}

進(jìn)階題目描述:給你兩個(gè) m x n 的二進(jìn)制矩陣 grid1 和 grid2 ,它們只包含 0 (表示水域)和 1 (表示陸地)荒揣。一個(gè) 島嶼 是由 四個(gè)方向 (水平或者豎直)上相鄰的 1 組成的區(qū)域篷角。任何矩陣以外的區(qū)域都視為水域。

如果 grid2 的一個(gè)島嶼系任,被 grid1 的一個(gè)島嶼 完全 包含恳蹲,也就是說(shuō) grid2 中該島嶼的每一個(gè)格子都被 grid1 中同一個(gè)島嶼完全包含,那么我們稱 grid2 中的這個(gè)島嶼為 子島嶼 俩滥。

請(qǐng)你返回 grid2 中 子島嶼 的 數(shù)目 嘉蕾。

示例 :

輸入:grid1 = [[1,1,1,0,0],[0,1,1,1,1],[0,0,0,0,0],[1,0,0,0,0],[1,1,0,1,1]], grid2 = [[1,1,1,0,0],[0,0,1,1,1],[0,1,0,0,0],[1,0,1,1,0],[0,1,0,1,0]]
輸出:3
解釋:如上圖所示,左邊為 grid1 霜旧,右邊為 grid2 错忱。
grid2 中標(biāo)紅的 1 區(qū)域是子島嶼,總共有 3 個(gè)子島嶼。

思路:只要grid2的島嶼在grid1上是水域的話航背,就不是子島嶼喉悴,所以只需要在dfs搜索過(guò)程中判斷是不是水域,如果不是水域玖媚,我們統(tǒng)計(jì)覆蓋子島嶼的個(gè)數(shù)箕肃。

代碼實(shí)現(xiàn):

public boolean flag = false;
public int countSubIslands(int[][] grid1, int[][] grid2) {
    int m = grid2.length, n = grid2[0].length;
    int ans = 0;

    for (int i = 0; i < m; ++i) {
        for (int j = 0; j < n; ++j) {
            if (grid2[i][j] == 1) {
                flag = true;
                dfs(grid1, grid2, i, j);
                if (flag) {
                    ans++;
                }
            }
        }
    }
    return ans;
}

public void dfs(int[][] grid1, int[][] grid2, int r, int c) {
    if (!inArea(grid1, grid2, r, c) || grid2[r][c] != 1) {
        return;
    }
    // 標(biāo)記為已遍歷
    grid2[r][c] = 2;
    if (grid1[r][c] == 0) {
        flag = false;
    }
    dfs(grid1, grid2, r - 1, c);
    dfs(grid1, grid2, r + 1, c);
    dfs(grid1, grid2, r, c - 1);
    dfs(grid1, grid2, r, c + 1);
}

private boolean inArea(int[][] grid1, int[][] grid2, int r, int c) {
    return 0 <= r && r < grid1.length && 0 <= c && c < grid1[0].length;
}

2.島嶼的最大面積(695 - 中)

題目描述:給定一個(gè)包含了一些 0 和 1 的非空二維數(shù)組 grid 。

一個(gè) 島嶼 是由一些相鄰的 1 (代表土地) 構(gòu)成的組合今魔,這里的「相鄰」要求兩個(gè) 1 必須在水平或者豎直方向上相鄰勺像。你可以假設(shè) grid 的四個(gè)邊緣都被 0(代表水)包圍著。

找到給定的二維數(shù)組中最大的島嶼面積错森。(如果沒(méi)有島嶼吟宦,則返回面積為 0 。)

示例 :

[[0,0,1,0,0,0,0,1,0,0,0,0,0],
 [0,0,0,0,0,0,0,1,1,1,0,0,0],
 [0,1,1,0,1,0,0,0,0,0,0,0,0],
 [0,1,0,0,1,1,0,0,1,0,1,0,0],
 [0,1,0,0,1,1,0,0,1,1,1,0,0],
 [0,0,0,0,0,0,0,0,0,0,1,0,0],
 [0,0,0,0,0,0,0,1,1,1,0,0,0],
 [0,0,0,0,0,0,0,1,1,0,0,0,0]]
對(duì)于上面這個(gè)給定矩陣應(yīng)返回 6涩维。注意答案不應(yīng)該是 11 殃姓,因?yàn)閸u嶼只能包含水平或垂直的四個(gè)方向的 1 。

思路:殊途同歸瓦阐,這里dfs返回的是四個(gè)方向島嶼的數(shù)量累加和蜗侈。

代碼實(shí)現(xiàn):

public int maxAreaOfIsland(int[][] grid) {
    if (grid == null || grid.length == 0 || (grid.length == 1 && grid[0].length == 0)) {
        return 0;
    }
    int M = grid.length, N = grid[0].length;
    int max = 0;

    for (int i = 0; i < M; i++) {
        for (int j = 0; j < N; j++) {
            if (grid[i][j] == 1) {
                int count = dfs(grid, i, j);
                max = Math.max(max, count);
            }
        }
    }
    return max;
}
private int dfs(int[][] grid, int r, int c) {
    if (!inArea(grid, r, c) || grid[r][c] != 1) return 0;
    grid[r][c] = 2; // 標(biāo)記為已遍歷

    return 1 +
    dfs(grid, r - 1, c) +
    dfs(grid, r + 1, c) +
    dfs(grid, r, c - 1) +
    dfs(grid, r, c + 1);
}
private boolean inArea(int[][] grid, int r, int c) {
    return 0 <= r && r < grid.length && 0 <= c && c < grid[0].length;
}

3.填海造陸(827 - 難)

題目描述:在二維地圖上, 0代表海洋睡蟋, 1代表陸地踏幻,我們最多只能將一格 0 海洋變成 1變成陸地。

進(jìn)行填海之后戳杀,地圖上最大的島嶼面積是多少该面?(上、下信卡、左隔缀、右四個(gè)方向相連的 1 可形成島嶼)

示例 :

輸入: [[1, 0], [0, 1]]
輸出: 3
解釋: 將一格0變成1,最終連通兩個(gè)小島得到面積為 3 的島嶼坐求。

思路:兩遍DFS求解:

  • 第一遍DFS:遍歷陸地格子蚕泽,計(jì)算島嶼的面積(同695題)并標(biāo)記島嶼(島嶼內(nèi)容更新為序號(hào)),
  • 第二遍DFS:遍歷海洋格子桥嗤,連接某一點(diǎn)上下左右的陸地格子须妻,四個(gè)方向用hashset去重(確保連接為有效區(qū)域并且不是海洋)。

作為上一題的升級(jí)版泛领,想一下:如何檢驗(yàn)要填充格子的相鄰格子是否來(lái)自兩個(gè)不同的島嶼荒吏。

可以使用島嶼序號(hào)與面積的對(duì)應(yīng)關(guān)系:<key, value> -> 島嶼序號(hào)-島嶼面積

注意:0表示海洋,1表示陸地渊鞋,所以島嶼的編號(hào)從2開(kāi)始绰更。

代碼實(shí)現(xiàn):

 public int largestIsland(int[][] grid) {
    if (grid==null || grid.length == 0){
        return 1;
    }

    int res = 0;
    int index = 2;  //index表示島嶼的編號(hào)瞧挤,0是海洋1是陸地,從2開(kāi)始遍歷
    HashMap<Integer,Integer> indexAndAreas = new HashMap<>();   //島嶼編號(hào):島嶼面積

    /**
     * 計(jì)算每個(gè)島嶼的面積儡湾,并標(biāo)記是第幾個(gè)島嶼
     */
    for (int r=0;r<grid.length;r++){
        for (int c=0;c<grid[0].length;c++){
            if (grid[r][c] == 1){
                int area = area(grid,r,c,index);    //返回每個(gè)島嶼的面積特恬,dfs(其中該位置更新為島嶼編號(hào))
                indexAndAreas.put(index,area);      //存入島嶼編號(hào)、島嶼面積
                index++;       //島嶼編號(hào)增加
                res = Math.max(res,area);   //記錄最大的島嶼面積
            }
        }
    }

    if (res == 0) return 1;     //res=0表示沒(méi)有陸地徐钠,那么造一塊癌刽,則返回1即可

    /**
     * 遍歷海洋格子,假設(shè)這個(gè)格子填充尝丐,那么就把上下左右是陸地的格子所在的島嶼連接起來(lái)
     */
    for (int r=0;r<grid.length;r++){
        for (int c=0;c<grid[0].length;c++){
            if (grid[r][c] == 0){ 
                HashSet<Integer> hashSet = findNeighbour(grid,r,c);//把上下左右鄰居放入set去重
                if (hashSet.size() < 1) continue;    //如果海洋格子周圍沒(méi)有格子不必計(jì)算
                int twoIsland = 1;      //填充這個(gè)格子显拜,初始為1,這個(gè)變量記錄合并島嶼后的面積
                for (Integer i: hashSet){
                    twoIsland += indexAndAreas.get(i);  //該格子填充爹袁,則上下左右的陸地的都連接了远荠,通過(guò)序號(hào)獲得面積,加上面積
                }
                res = Math.max(res,twoIsland);  //比較得到最大的面積
            }
        }
    }
    return res;
}


/**
 * 對(duì)于海洋格子失息,找到上下左右
 * 每個(gè)方向譬淳,都要確保有效inArea以及是陸地格子,則表示是該海洋格子的陸地鄰居(返回當(dāng)前海洋格子的陸地鄰居集合根时!)
 */
private HashSet<Integer> findNeighbour(int[][] grid,int r,int c){
    HashSet<Integer> hashSet = new HashSet<>();
    if (inArea(grid,r-1,c)&&grid[r-1][c] != 0){
        hashSet.add(grid[r-1][c]);
    }
    if (inArea(grid,r+1,c) && grid[r+1][c] != 0){
        hashSet.add(grid[r+1][c]);
    }
    if (inArea(grid,r,c-1) && grid[r][c-1] != 0){
        hashSet.add(grid[r][c-1]);
    }
    if (inArea(grid,r,c+1) && grid[r][c+1] != 0){
        hashSet.add(grid[r][c+1]);
    }
    return hashSet;
}

/**
 * dfs方法瘦赫,將格子填充為index,即表示這個(gè)格子屬于哪個(gè)島的
 * 計(jì)算島嶼面積蛤迎,上下左右,當(dāng)然這個(gè)可以優(yōu)化的含友,因?yàn)椴恍枰?jì)算上面的替裆,會(huì)有重復(fù)
 */
private int area(int[][] grid, int r, int c,int index){
    if (!inArea(grid,r,c) || grid[r][c] != 1) return 0;
    grid[r][c] = index; //設(shè)置當(dāng)前格子為某個(gè)島嶼編號(hào)

    return 1 + area(grid,r-1,c,index) + area(grid,r+1,c,index) + area(grid,r,c-1,index) + area(grid,r,c+1,index);
}

private boolean inArea(int[][] grid,int r,int c){
    return r>=0 && r<grid.length&&c>=0 && c<grid[0].length;
}

4.島嶼的周長(zhǎng)(463 - 易)

題目描述:給定一個(gè) row x col 的二維網(wǎng)格地圖 grid ,其中:gridi = 1 表示陸地窘问, gridi = 0 表示水域辆童。

網(wǎng)格中的格子 水平和垂直 方向相連(對(duì)角線方向不相連)。整個(gè)網(wǎng)格被水完全包圍惠赫,但其中恰好有一個(gè)島嶼(或者說(shuō)把鉴,一個(gè)或多個(gè)表示陸地的格子相連組成的島嶼)。

島嶼中沒(méi)有“湖”(“湖” 指水域在島嶼內(nèi)部且不和島嶼周圍的水相連)儿咱。格子是邊長(zhǎng)為 1 的正方形庭砍。網(wǎng)格為長(zhǎng)方形,且寬度和高度均不超過(guò) 100 混埠。計(jì)算這個(gè)島嶼的周長(zhǎng)怠缸。

示例 :

輸入:grid = [[0,1,0,0],[1,1,1,0],[0,1,0,0],[1,1,0,0]]
輸出:16
解釋:它的周長(zhǎng)是上面圖片中的 16 個(gè)黃色的邊

思路:周長(zhǎng)本質(zhì)就是島嶼的邊緣,計(jì)算島嶼的周長(zhǎng)只有兩種情況:一種是區(qū)域越界钳宪,另一種是遇到海洋揭北。

代碼實(shí)現(xiàn):

public int islandPerimeter(int[][] grid) {
    if (grid == null || grid.length == 0 || (grid.length == 1 && grid[0].length == 0)) {
        return 0;
    }
    int M = grid.length, N = grid[0].length;
    int ans = 0;

    for (int i = 0; i < M; i++) {
        for (int j = 0; j < N; j++) {
            if (grid[i][j] == 1) {
                ans = dfs(grid, i, j);
            }
        }
    }
    return ans;
}
private int dfs(int[][] grid, int r, int c) {
    if (!inArea(grid, r, c) || grid[r][c] == 0) return 1;
    if (grid[r][c] == 2) return 0;
    grid[r][c] = 2; // 標(biāo)記為已遍歷

    return
    dfs(grid, r - 1, c) +
    dfs(grid, r + 1, c) +
    dfs(grid, r, c - 1) +
    dfs(grid, r, c + 1);
}
private boolean inArea(int[][] grid, int r, int c) {
    return 0 <= r && r < grid.length && 0 <= c && c < grid[0].length;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末扳炬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子搔体,更是在濱河造成了極大的恐慌恨樟,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疚俱,死亡現(xiàn)場(chǎng)離奇詭異厌杜,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)计螺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門夯尽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人登馒,你說(shuō)我怎么就攤上這事匙握。” “怎么了陈轿?”我有些...
    開(kāi)封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵圈纺,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我麦射,道長(zhǎng)蛾娶,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任潜秋,我火速辦了婚禮蛔琅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘峻呛。我一直安慰自己罗售,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布钩述。 她就那樣靜靜地躺著寨躁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪牙勘。 梳的紋絲不亂的頭發(fā)上职恳,一...
    開(kāi)封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音方面,去河邊找鬼放钦。 笑死,一個(gè)胖子當(dāng)著我的面吹牛葡幸,可吹牛的內(nèi)容都是我干的最筒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蔚叨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼床蜘!你這毒婦竟也來(lái)了辙培?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤邢锯,失蹤者是張志新(化名)和其女友劉穎扬蕊,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體丹擎,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡尾抑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蒂培。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片再愈。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖护戳,靈堂內(nèi)的尸體忽然破棺而出翎冲,到底是詐尸還是另有隱情,我是刑警寧澤媳荒,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布抗悍,位于F島的核電站,受9級(jí)特大地震影響钳枕,放射性物質(zhì)發(fā)生泄漏缴渊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一鱼炒、第九天 我趴在偏房一處隱蔽的房頂上張望衔沼。 院中可真熱鬧,春花似錦田柔、人聲如沸俐巴。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至擎鸠,卻和暖如春缀磕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背劣光。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工袜蚕, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人绢涡。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓牲剃,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親雄可。 傳聞我的和親對(duì)象是個(gè)殘疾皇子凿傅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容