【Leetcode】岛屿问题——dfs算法

岛屿问题是dfs回溯算法中的经典问题,以下就是对岛屿问题相关的问题进行汇总。

岛屿问题

463. 岛屿的周长

1.题目描述

leetcode题目链接:463. 岛屿的周长
在这里插入图片描述

2.思路分析

用len表示周长,顺序遍历二维矩阵,当遇到岛屿即grid[i][j] == 1时,执行递归

  • 如果为陆地grid[i][j] == 1时,令grid[i][j] == -1,然后递归遍历四个方向
  • 遇到边界、或者水域grid[i][j] == 0时len+1(这样只会算最外围的周长)

3.参考代码

class Solution {
    public int len = 0;
    public int islandPerimeter(int[][] grid) {
        int m = grid.length, n = grid[0].length;
        for (int i  = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 1) {
                    dfs(grid, i, j);
                }
            }
        }
        return len;
    }
    public void dfs (int[][] grid, int i, int j) {
        int m = grid.length, n = grid[0].length;
        if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] == 0) {
            len++;
        } else if (grid[i][j] == 1){
            grid[i][j] = -1;
            dfs(grid, i + 1, j);
            dfs(grid, i - 1, j);
            dfs(grid, i, j + 1);
            dfs(grid, i, j - 1);
        }
    }
}
200. 岛屿数量

1.题目描述

leetcode题目链接:200. 岛屿数量
在这里插入图片描述

2.思路分析

方法一:DFS

顺序遍历,若为岛屿 grid[i][j] == 1,则res++,然后将与此位置岛屿相连的陆地置为1,

dfs()思路如下

  • 若遇到边界或者水域,终止
  • 令陆地变为水域grid[i][j] == 0,然后递归遍历四个方向

方法二:并查集

初始时将每一个“1”的格子看作一个岛。然后遍历整个格,考察该格子右侧和下侧的格子,如果也是“1”,将其合并到当前格子所在的岛中,每次合并都累计合并次数。岛屿数量就是最初单个格子岛屿数量减去合并次数。

需要注意初始化时要将二维grid[i][j]映射到一维id[k]中,k = i * n + j,n是grid的列数。初始化id: grid[i][j] = 1时 id[k] = k,sz[k] = 1

3.参考代码

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

方法二:并查集

class Solution {
    int landCount = 0;
    int mergeCount = 0;
    public int numIslands(char[][] grid) {
        // 并查集
        int m = grid.length, n = grid[0].length;
        UF uf = new UF(grid);
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                int land = i * n + j;
                if (grid[i][j] == '1') {
                    if (j < n - 1 && grid[i][j + 1] == '1') {  // 右侧
                        uf.union(land, land + 1);
                    }
                    if (i < m - 1 && grid[i + 1][j] == '1') {  // 下侧
                        uf.union(land, land + n);
                    }
                }
            }
        }
        return landCount - mergeCount;

    }
    class UF {  // 路径压缩的加权quick-union算法模板
        int[] id;
        int[] sz;

        private UF (char[][] grid) {
            int m = grid.length, n = grid[0].length;
            id = new int[m * n];
            sz = new int[m * n];
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    if (grid[i][j] == '1') {  // 累计单个格子岛屿数量
                        landCount++; 
                        int k = i * n + j;
                        id[k] = k;
                        sz[k] = 1;
                    }
                }
            }
        }

        public void union (int p, int q) {
            int pRoot = find(p);
            int qRoot = find(q);
            if (pRoot != qRoot) {
                mergeCount++;  // 不属于一个集合时,合并次数加1
                if (sz[pRoot] < sz[qRoot]) {
                    id[pRoot] = qRoot;
                    sz[qRoot] += sz[pRoot];
                } else {
                    id[qRoot] = pRoot;
                    sz[pRoot] += sz[qRoot];
                }
            }
        }

        private int find (int p) {
            if (p == id[p]) {
                return p;
            }
            id[p] = find(id[p]);
            return id[p];
        }
    }
}
305. 岛屿数量Ⅱ

1.题目描述

leetcode题目链接:plus会员题目,可以直接看题目描述。
在这里插入图片描述

输入: m = 3, n = 3, 
	positions = [[0,0], [0,1], [1,2], [2,1]]
输出: [1,1,2,3]
解析:
起初,二维网格 grid 被全部注入「水」。(0 代表「水」,1 代表「陆地」)
0 0 0
0 0 0
0 0 0

操作 #1addLand(0, 0) 将 grid[0][0] 的水变为陆地。
1 0 0
0 0 0   Number of islands = 1
0 0 0

操作 #2addLand(0, 1) 将 grid[0][1] 的水变为陆地。
1 1 0
0 0 0   岛屿的数量为 1
0 0 0

操作 #3addLand(1, 2) 将 grid[1][2] 的水变为陆地。
1 1 0
0 0 1   岛屿的数量为 2
0 0 0

操作 #4addLand(2, 1) 将 grid[2][1] 的水变为陆地。
1 1 0
0 0 1   岛屿的数量为 3
0 1 0

2.思路分析

方法一:暴力搜索

每进行一次操作,全图遍历一次,调用岛屿数量的代码即可。

方法二:并查集

不使用路径压缩的加权的并查集模板,在函数内部初始化及合并。

3.参考代码

方法一:暴力搜索

class Solution {
    public int[] numIslands2(int m, int n, int[][] positions) {
        int[] res = new int[positions.length()];
        char[][] grid = new int[m][n];
        for(int i = 0; i < positions.length; i++) {
            int e = positions[i];
            grid[e[0]][e[1]] = '1';
            //此处调用岛屿数量1中函数
            res[i] = numIslands(grid);
        }
        return res;
    }
}

方法二:并查集

class Solution {
    public int[] numIslands2(int m, int n, int[][] positions) {
        int[] res = new int[positions.length];
        int[] id = new int[m * n];  //将二维矩阵压缩为一维父链接数组
        int[] dirs = {0, -1, 0, 1, 0};  //将二维方向压缩为一维
        int num = 0;    //初始岛屿数为0
        char[][] grid = new char[m][n];
        
        for(int i = 0; i < positions.length; i++) {
            int[] e = positions[i];
            
            //初始化
            grid[e[0]][e[1]] = '1';
            int p = e[0] * n + e[1];
            id[p] = p;
            num += 1;
            
            //遍历上下左右四个方向,若为岛屿则合并
            for(int k = 0; k < dirs.length - 1; k++) {
            	int x = e[0] + dirs[k];
            	int y = e[1] + dirs[k + 1];
            	if(x >= 0 && x < m && y >= 0 && y < n && grid[x][y] == '1') {
            		int q = x * n  + y;
                	int qroot = find(id, q);
                	//合并
                	id[qroot] = p;
                	//岛屿数量减一
                	num -= 1;
            	}
            }
            
            res[i] = num;
        }
        return res;
    }
       
    private int find(int[] id, int p) {
    	if(p == id[p]) return p;
    	return id[p];
    }
}

695. 岛屿的最大面积

1.题目描述

leetcode题目链接:695. 岛屿的最大面积
在这里插入图片描述

2.思路分析

与岛屿数量一样,在将岛屿置为0时,统计岛屿的面积。然后更新最大面积。本题可参考华为机试:华为机试:最大岛屿体积

3.参考代码

class Solution {
    int count;
    public int maxAreaOfIsland(int[][] grid) {
        int m = grid.length, n = grid[0].length;
        int res = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 1) {
                    count = 0;
                    dfs(grid, i , j);
                    res = Math.max(res, count);
                }
            }
        }
        return res;
    }
    public void dfs(int[][] grid, int i, int j) {
        int m = grid.length, n = grid[0].length;
        if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] == 0) {
            return ;
        }
        count++;
        grid[i][j] = 0;
        dfs(grid, i + 1, j);
        dfs(grid, i - 1, j);
        dfs(grid, i, j + 1);
        dfs(grid, i, j - 1);
    }
}
1254. 统计封闭岛屿的数目

1.题目描述

leetcode题目链接:1254. 统计封闭岛屿的数目
在这里插入图片描述

2.思路分析

与岛屿数量不同的的是,这道题边界的岛屿不算岛屿。周围都为水域时才算岛屿。

陆地越过边界条件则不是岛屿,返回false

if (i < 0 || i >= m || j < 0 || j >= n) {
     return false;
 }

陆地边界遇到水域,返回true

if (grid[i][j] == 1) {
    return true;
}

需要注意的是,dfs上下左右的时候有两种写法:

boolean top = dfs(grid, i, j - 1);
boolean bottom = dfs(grid, i, j + 1);
boolean left = dfs(grid, i - 1, j);
boolean right = dfs(grid, i + 1, j);
return top && bottom && left && right;

或者:

return dfs(grid, i, j - 1) & dfs(grid, i, j + 1) & dfs(grid, i - 1, j) & dfs(grid, i + 1, j);

注意:这里不能直接返回&&相连的

  • Java 中 && 和 & 都是表示与的逻辑运算符,都表示逻辑运输符 and,当两边都为 true 的时候,整个运算结果才为 true ,否则为 false 。
  • && :有短路功能,当第⼀个表达式的值为 false 的时候,则不再计算第⼆个表达式
  • & :不管第⼀个表达式结果是否为 true ,第⼆个都会执⾏

我们的目的是让上下左右都dfs过以后再判断。

方法二:先去除与边界相连的岛屿,再统计岛屿数量。

// 先去除与边界相连岛屿
for (int i = 0; i < m; i++) { //遍历第一列和最后一列
    dfs(grid, i, 0);
    dfs(grid, i, n - 1);
}
for (int j = 0; j < n; j++) { //遍历第一行和最后一行
    dfs(grid, 0, j);
    dfs(grid, m - 1, j);
}

统计岛屿数量则与之前代码相同。

3.参考代码

class Solution {
    public int closedIsland(int[][] grid) {
        // 与岛屿数量相同,只是不考虑与边界相连
        int m = grid.length, n = grid[0].length;
        int res = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 0 && dfs(grid, i, j)) {
                    res++;
                }
            }
        }
        return res;

    }
    public boolean dfs(int[][] grid, int i, int j) {
        int m = grid.length, n = grid[0].length;
        if (i < 0 || i >= m || j < 0 || j >= n) {
            return false;
        }
        if (grid[i][j] == 1) {
            return true;
        }
        grid[i][j] = 1;
        boolean top = dfs(grid, i, j - 1);
        boolean bottom = dfs(grid, i, j + 1);
        boolean left = dfs(grid, i - 1, j);
        boolean right = dfs(grid, i + 1, j);
        return top && bottom && left && right;
        // return dfs(grid, i, j - 1) & dfs(grid, i, j + 1) & dfs(grid, i - 1, j) & dfs(grid, i + 1, j);
    }
}

先去除与边界相连的岛屿,再统计岛屿数量

class Solution {
    public int closedIsland(int[][] grid) {
        // 与岛屿数量相同,只是不考虑与边界相连
        // 先去除与边界相连的岛屿,再统计岛屿的数量
        int m = grid.length, n = grid[0].length;
        // 先去除与边界相连岛屿
        for (int i = 0; i < m; i++) { //遍历第一列和最后一列
            dfs(grid, i, 0);
            dfs(grid, i, n - 1);
        }
        for (int j = 0; j < n; j++) { //遍历第一行和最后一行
            dfs(grid, 0, j);
            dfs(grid, m - 1, j);
        }
        // 再统计岛屿数量
        int res = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 0) {
                    res++;
                    dfs(grid, i, j);
                }
            }
        }
        return res;

    }
    public void dfs(int[][] grid, int i, int j) {
        int m = grid.length, n = grid[0].length;
        if (i < 0 || j < 0 || i >= m || j >= n || grid[i][j] == 1) {
            return;
        }
        grid[i][j] = 1;
        dfs(grid, i + 1, j);
        dfs(grid, i - 1, j);
        dfs(grid, i, j + 1);
        dfs(grid, i, j - 1);
    }
}
827. 最大人工岛

1.题目描述

leetcode题目链接:827. 最大人工岛
在这里插入图片描述

2.思路分析

对网格做两遍 DFS:

  • 第一遍 DFS 遍历陆地格子,计算每个岛屿的面积并标记岛屿;
  • 第二遍 DFS 遍历海洋格子,观察每个海洋格子相邻的岛屿格子。重点!!!把每个岛屿格子的值标为面积

0是海洋1是岛屿
在这里插入图片描述
0是海洋,2,3,4,5,6是岛屿的序号
在这里插入图片描述
0是海洋,其他数字的岛屿的面积
在这里插入图片描述
岛屿序号和面积的对应关系,即代码中的hashmap:
在这里插入图片描述

3.参考代码

class Solution {
    public int largestIsland(int[][] grid) {
        int n = grid.length;
        if (n == 0) return 1;
        int res = 0;
        int index = 2; // index表示岛屿的编号,0是海洋1是陆地,从2开始遍历
        HashMap<Integer,Integer> indexAndAreas = new HashMap<>(); // 岛屿编号:岛屿面积
        /**
         * 计算每个岛屿的面积,并标记是第几个岛屿
         */
        for (int i = 0; i < n; i++){
            for (int j = 0; j < n; j++){
                if (grid[i][j] == 1){  // 遍历没有访问过的岛屿格子
                    int area = dfs(grid, i, j, index);  // 返回每个岛屿的面积,dfs
                    indexAndAreas.put(index,area); // 存入岛屿编号、岛屿面积
                    index++; // 岛屿编号增加
                    res = Math.max(res,area); // 记录最大的岛屿面积
                }
            }
        }
        if (res == 0) return 1;  // res=0 表示没有陆地,那么造一块,则返回1即可

        /**
         * 遍历海洋格子,假设这个格子填充,那么就把上下左右是陆地的格子所在的岛屿连接起来
         */
        for (int i = 0; i < n; i++){
            for (int j = 0; j < n; j++){
                if (grid[i][j] == 0){ // 遍历海洋格子
                    HashSet<Integer> hashSet = findNeighbour(grid, i, j);  // 把上下左右邻居放入set去重
                    if (hashSet.size() < 1) continue; // 如果海洋格子周围没有格子不必计算
                    int twoIsland = 1; // 填充这个格子,初始为1,这个变量记录合并岛屿后的面积
                    for (Integer key: hashSet){
                        twoIsland += indexAndAreas.get(key);//该格子填充,则上下左右的陆地的都连接了,通过序号获得面积,加上面积
                    }
                    res = Math.max(res,twoIsland);//比较得到最大的面积
                }
            }
        }
        return res;
    }

    /**
     * 对于海洋格子,找到上下左右
     * 每个方向,都要确保有效inArea以及是陆地格子,则表示是该海洋格子的陆地邻居
     */
    private HashSet<Integer> findNeighbour(int[][] grid, int i, int j){
        HashSet<Integer> hashSet = new HashSet<>();
        if (inArea(grid, i - 1, j) && grid[i - 1][j] != 0){
            hashSet.add(grid[i - 1][j]);
        }
        if (inArea(grid, i + 1, j) && grid[i + 1][j] != 0){
            hashSet.add(grid[i + 1][j]);
        }
        if (inArea(grid, i, j - 1) && grid[i][j - 1] != 0){
            hashSet.add(grid[i][j - 1]);
        }
        if (inArea(grid, i, j + 1) && grid[i][j + 1] != 0){
            hashSet.add(grid[i][j + 1]);
        }
        return hashSet;
    }

    /**
     * dfs方法,将格子填充为index,即表示这个格子属于哪个岛的
     * 计算岛屿面积,上下左右,当然这个可以优化的,因为不需要计算上面的,会有重复
     */
    private int dfs(int[][] grid, int i, int j, int index){
        if (!inArea(grid, i, j)){
            return 0;
        }
        //不为1,表示为海洋格子或者已经遍历过了
        if (grid[i][j] != 1){
            return 0;
        }
        grid[i][j] = index;//设置当前格子为某个岛屿编号
        return 1 + dfs(grid, i - 1, j, index) + dfs(grid, i + 1, j, index) + dfs(grid, i , j - 1, index) + dfs(grid, i, j + 1, index);
    }

    /**
     * 判断grid[i][j]是否大小合适
     */
    private boolean inArea(int[][] grid, int i, int j){
        return i >= 0 && i < grid.length && j >= 0 && j < grid[0].length;
    }

}

参考:

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值