LeetCode刷题笔记-岛屿问题 DFS

LeetCode刷题笔记-岛屿问题


LeetCode中有许多岛屿类问题,如以下题目:

200. 岛屿数量
463. 岛屿的周长
695. 岛屿的最大面积
827. 最大人工岛

这些题目基本上都是给一个二维数组作为网格,0为海洋1为陆地,然后要你求诸如岛屿最大数量面积周长等问题。

接下来讨论一下此类问题的一个通用解法-DFS。
LeetCode200. 岛屿数量 为例:

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。
示例 :

输入:grid = [ [“1”,“1”,“1”,“1”,“0”], [“1”,“1”,“0”,“1”,“0”],
[“1”,“1”,“0”,“0”,“0”], [“0”,“0”,“0”,“0”,“0”] ] 输出:1

很容易有如下想法:首先判断一个格子是否为陆地,若是则向上下左右搜索,直到碰到海洋为止,所有搜索到的陆地就是该岛屿的组成部分。
那么该怎么搜索呢?
在二叉树中的搜索可以采用dfs方法,岛屿问题可以看成在一棵四叉树中进行dfs搜索,大体思路与二叉树中的dfs相同,
很容易写出以下代码

void dfs(char[][]grid, int i, int j) {
    
      //朝上下左右四个方向搜索     
        if(grid[i][j] == '1') {
    
    
            dfs(grid, i + 1, j);
            dfs(grid, i - 1, j);
            dfs(grid, i, j + 1);
            dfs(grid, i, j - 1);
        }
    }

二叉树中搜索停止的条件是当前节点为null,在网格中停止的条件也类似,考虑到网格有边界,因此可以把网格边界的格子看成二叉树的叶节点,对上面的dfs进行修改

void dfs(char[][]grid, int i, int j) {
    
    
        int m = grid.length;
        int n = grid[0].length;
        if(i >= m || i < 0 || j >= n || j < 0) {
    
    //若超出边界,则停止继续搜索并返回
            return;
        }
        if(grid[i][j] == '1') {
    
    
            dfs(grid, i + 1, j);
            dfs(grid, i - 1, j);
            dfs(grid, i, j + 1);
            dfs(grid, i, j - 1);
        }
    }

然而这样还有一个问题,遍历的时候会访问到已搜索过的节点,二叉树中通常使用一个visit[]数组来标记节点是否遍历过,在此由于不需要访问节点的值,因此直接把搜索过的节点值设置为2,当访问到节点值为2的点说明该点已经是某个岛屿的一部分了,就将其跳过
最终的dfs算法如下

void dfs(char[][]grid, int i, int j) {
    
    
        int m = grid.length;
        int n = grid[0].length;
        if(i >= m || i < 0 || j >= n || j < 0) {
    
    
            return;
        }
        if(grid[i][j] == '1') {
    
    
            grid[i][j] = '2';
            dfs(grid, i + 1, j);
            dfs(grid, i - 1, j);
            dfs(grid, i, j + 1);
            dfs(grid, i, j - 1);
        }
    }

接下来问题就很好解决了,遍历网格中的点,若为陆地则以该陆地为起点进行dfs搜索,把搜索到的点的值改为2,完成一次从值为1的点开始的搜索说明找到了一个新的岛屿,将岛屿数量加1,最后count值即为所求
完整代码如下

class Solution {
    
    
    void dfs(char[][]grid, int i, int j) {
    
    
        int m = grid.length;
        int n = grid[0].length;
        if(i >= m || i < 0 || j >= n || j < 0) {
    
    
            return;
        }
        if(grid[i][j] == '1') {
    
    
            grid[i][j] = '2';
            dfs(grid, i + 1, j);
            dfs(grid, i - 1, j);
            dfs(grid, i, j + 1);
            dfs(grid, i, j - 1);
        }
    }
    public int numIslands(char[][] grid) {
    
    
        int m = grid.length;
        int n = grid[0].length;
        int count = 0;
        for(int i = 0; i < m; i++) {
    
    
            for(int j = 0; j < n; j++) {
    
    
                if(grid[i][j] == '1') {
    
    
                    count++;
                    dfs(grid, i, j);
                }
            }
        }
        return count;

    }
}

接着看下一题
LeetCode463. 岛屿的周长

给定一个 row x col 的二维网格地图 grid ,其中:grid[i][j] = 1 表示陆地, grid[i][j] = 0
表示水域。

网格中的格子 水平和垂直
方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。

岛屿中没有“湖”(“湖” 指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100
。计算这个岛屿的周长。
示例:
在这里插入图片描述
输入:grid = [[0,1,0,0],[1,1,1,0],[0,1,0,0],[1,1,0,0]]
输出:16
解释:它的周长是上面图片中的 16 个黄色的边

这题可以用简单的遍历解决,但在此我们用dfs解法做。
首先分析岛屿周长但组成,不难发现,岛屿的周长就是岛屿最边缘块的边长之和,岛屿边缘块也就是dfs时返回的块,包括遇到边界和遇到海洋两种情况,由此可对之前的dfs稍加修改:

int dfs(int[][]grid, int i, int j) {
    
    
        int m = grid.length;
        int n = grid[0].length;
        if(i >= m || i < 0 || j >= n || j < 0 || grid[i][j] == 0) {
    
    //若下一个方向的块是边界或海洋,则这个方向的边是边界的一部分
            return 1;
        }
        if(grid[i][j] == 1) {
    
    
            grid[i][j] = 2;
            return
            dfs(grid, i + 1, j)+
            dfs(grid, i - 1, j) +
            dfs(grid, i, j + 1) +
            dfs(grid, i, j - 1);
        }
    }

总的代码如下:

class Solution {
    
    
    int dfs(int[][]grid, int i, int j) {
    
    
        int m = grid.length;
        int n = grid[0].length;
        if(i >= m || i < 0 || j >= n || j < 0 || grid[i][j] == 0) {
    
    //若下一个方向的块是边界或海洋,则这个方向的边是边界的一部分
            return 1;
        }
        if(grid[i][j] == 1) {
    
    
            grid[i][j] = 2;
            return dfs(grid, i + 1, j) + 
                dfs(grid, i - 1, j) +
                dfs(grid, i, j + 1) +
                dfs(grid, i, j - 1);
        }
        return 0;
    }
    public int islandPerimeter(int[][] grid) {
    
    
        int m = grid.length;
        int n = grid[0].length;
        for(int i = 0; i < m; i++) {
    
    
            for(int j = 0; j < n; j++) {
    
    
                if(grid[i][j] == 1) {
    
    
                    return dfs(grid, i, j);
                }
            }
        }
        return 0;
    }
}

接着看下一题:LeetCode 695. 岛屿的最大面积

给定一个包含了一些 0 和 1 的非空二维数组 grid 。

一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid
的四个边缘都被 0(代表水)包围着。

找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0 。)

示例 1:

[[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]]
对于上面这个给定矩阵应返回 6。注意答案不应该是 11 ,因为岛屿只能包含水平或垂直的四个方向的 1 。

有了前两题的经验这题就很简单了,搜索的时候每搜索到一块陆地就增加岛屿面积,最后返回所有的岛屿中面积最大值即可

参考代码如下:

class Solution {
    
    

    int dfs(int[][]grid, int i, int j) {
    
    
        int m = grid.length;
        int n = grid[0].length;
        if(i >= m || i < 0 || j >= n || j < 0 || grid[i][j] == 0) {
    
    //若下一个方向的块是边界或海洋,则这个方向的边是边界的一部分
            return 0;
        }
        if(grid[i][j] == 1) {
    
    
            grid[i][j] = 2;
            return dfs(grid, i + 1, j) + 
                dfs(grid, i - 1, j) +
                dfs(grid, i, j + 1) +
                dfs(grid, i, j - 1) + 1;
        }
        return 0;
    }

    public int maxAreaOfIsland(int[][] grid) {
    
    
        int m = grid.length;
        int 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) {
    
    
                    max = Math.max(max, dfs(grid, i, j));
                }
            }
        }
        return max;

    }
}

最后一题是LeetCode 827. 最大人工岛

先来看一下题目描述: 在二维地图上, 0代表海洋, 1代表陆地,我们最多只能将一格 0 海洋变成 1变成陆地。

进行填海之后,地图上最大的岛屿面积是多少?(上、下、左、右四个方向相连的 1 可形成岛屿)

示例 1:

输入: [[1, 0], [0, 1]] 输出: 3
解释: 将一格0变成1,最终连通两个小岛得到面积为 3 的岛屿。

示例 2:

输入: [[1, 1], [1, 0]] 输出: 4
解释: 将一格0变成1,岛屿的面积扩大为 4。

这题乍一看和之前的几题都不一样,但实际上还是能用相同的方法解决
首先自然要求出每块岛屿的面积,这与上一题相同,很容易解决

//dfs1用来求各个岛屿的面积
 int dfs1(int[][]grid, int i, int j) {
    
    
        int m = grid.length;
        int n = grid[0].length;
        if(i >= m || i < 0 || j >= n || j < 0 || grid[i][j] == 0) {
    
    //若下一个方向的块是边界或海洋,则这个方向的边是边界的一部分
            return 0;
        }
        if(grid[i][j] == 1) {
    
    
            grid[i][j] = 2;
            return dfs1(grid, i + 1, j) + 
                dfs1(grid, i - 1, j) +
                dfs1(grid, i, j + 1) +
                dfs1(grid, i, j - 1) + 1;
        }
        return 0;
    }

接下来的问题是连接哪两块岛屿才能使得总面积最大,从陆地下手不太好判断相隔一块海洋的两个岛屿,因此这次从海洋下手,对海洋进行dfs,若一块岛屿四个方向有两块是岛屿,则计算他们的面积和,取最大值即可。
此处的难点在于如何获取相邻岛屿的面积,一种想法是给该岛屿的每块标上面积,但这种方法无法区分一个岛屿的两个相隔块,同时因为岛屿的面积是在搜索中动态更新的,给搜索过的点赋值也是一个麻烦的问题。
因此考虑设计一个面积数组,用来保存各块岛屿的面积,


int getArea(int[][]grid, int i, int j) {
    
    
        int m = grid.length;
        int n = grid[0].length;
        if(i >= m || i < 0 || j >= n || j < 0) {
    
    
            return 0;
        }
         //index里存的是相邻四个块的类型索引,边界和海洋都为0,岛屿则为对应岛屿编号
        Set<Integer> index = new HashSet<>();//用set过滤属于同一岛屿的部分,
       
        if(grid[i][j] == 0) {
    
    
            index.add(i < m - 1?grid[i + 1][j]:0);
            index.add(i > 0?grid[i - 1][j]:0);
            index.add(j < n - 1?grid[i][j + 1]:0);
            index.add(j > 0?grid[i][j - 1]:0);
            int total = 0;
            for(int k :index) {
    
    
                if(k != 0) {
    
    
                    total += area.get(k - 2);//0和1被占用,岛屿编号从2开始
                }
            }
            return total + 1;
        }
        return 0;

    }

总的代码如下:

class Solution {
    
    
    List<Integer> area = new ArrayList<>();

    int dfs1(int[][]grid, int i, int j, int index) {
    
    
        int m = grid.length;
        int n = grid[0].length;
        if(i >= m || i < 0 || j >= n || j < 0 || grid[i][j] == 0) {
    
    
            return 0;
        }
        if(grid[i][j] == 1) {
    
    
            grid[i][j] = index;
            return dfs1(grid, i + 1, j, index) + 
                dfs1(grid, i - 1, j, index) +
                dfs1(grid, i, j + 1, index) +
                dfs1(grid, i, j - 1, index) + 1;
        }
        return 0;
    }

    int getArea(int[][]grid, int i, int j) {
    
    
        int m = grid.length;
        int n = grid[0].length;
        if(i >= m || i < 0 || j >= n || j < 0) {
    
    
            return 0;
        }
        Set<Integer> index = new HashSet<>();
        if(grid[i][j] == 0) {
    
    
            index.add(i < m - 1?grid[i + 1][j]:0);
            index.add(i > 0?grid[i - 1][j]:0);
            index.add(j < n - 1?grid[i][j + 1]:0);
            index.add(j > 0?grid[i][j - 1]:0);
            int total = 0;
            for(int k :index) {
    
    
                if(k != 0) {
    
    
                    total += area.get(k - 2);
                }
            }
            return total + 1;
        }
        return 0;

    }

    public int largestIsland(int[][] grid) {
    
    
        int m = grid.length;
        int n = grid[0].length;
        
        int max = 0;
        int index = 2;
        for(int i = 0; i < m; i++) {
    
    
            for(int j = 0; j < n; j++) {
    
    
                if(grid[i][j] == 1) {
    
    
                   area.add(dfs1(grid, i, j, index));
                   index++;
                }
            }
        }
        for(int i = 0; i < m; i++) {
    
    
            for(int j = 0; j < n; j++) {
    
    
                if(grid[i][j] == 0) {
    
    
                   max = Math.max(max, getArea(grid, i, j));
                }
            }
        }
        return max == 0?m * n:max;
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_42970016/article/details/112968292