Leetcode200、岛屿数量

本文详细介绍了LeetCode200题的解决方案,包括深度优先遍历、广度优先遍历和并查集三种方法。每个方法的时间和空间复杂度都进行了分析,深度优先和广度优先的时间复杂度皆为O(MN),空间复杂度为O(MN);并查集的时间复杂度为O(MN*α(MN)),空间复杂度为O(MN)。文中还解释了并查集中的find和union操作。

Leetcode200、岛屿数量

下面将要使用三个方法来解题

方法一、深度优先遍历

时间复杂度:O(MN),其中 MM 和 NN 分别为行数和列数
空间复杂度:O(MN),visited数组

class Solution {
    /**
        1、深度优先遍历
        每一次dfs都是把相邻位置的岛屿变成已经访问过,可以进行几次dfs,
        就有几个岛屿。
     */
    int[][] dirs = {{-1,0},{1,0},{0,-1},{0,1}};

    public int numIslands(char[][] grid) {
        int m = grid.length, n = grid[0].length;
        boolean[][] visited = new boolean[m][n];
        int ans = 0;

        for(int i = 0; i < m; i++) {
            for(int j = 0; j < n; j++) {
                if(grid[i][j] == '1' && !visited[i][j]) {
                    dfs(grid, visited, i, j);
                    ans++;
                }
            }
        }

        return ans;
    }

    /**
        需要注意的点:在进入方法的时候,除了要判断row和column是否合法以及当前位置是否已经遍历过以外,还需要
        判断当前位置是不是岛屿,不是岛屿也要返回。
     */
    public void dfs(char[][] grid, boolean[][] visited, int row, int column) {
        if(row < 0 || row >= grid.length || column < 0 || column >= grid[0].length || visited[row][column]
        || grid[row][column] == '0') {
            return;
        }

        if(grid[row][column] == '1') {
            visited[row][column] = true;
        }

        for(int i = 0; i < 4; i++) {
            int r = row + dirs[i][0];
            int c = column + dirs[i][1];
            dfs(grid, visited, r, c);
        }
    }
}

方法二、广度优先遍历

时间复杂度:O(MN),其中 MM 和 NN 分别为行数和列数
空间复杂度:O(MN),visited数组

class Solution {

    int[][] dirs = {{-1,0},{1,0},{0,-1},{0,1}};

    /**
        2、广度优先遍历
     */
    public int numIslands(char[][] grid) {
        Queue<int[]> queue = new LinkedList<>();
        int m = grid.length, n = grid[0].length;
        boolean[][] visited = new boolean[m][n];
        int ans = 0;

        for(int i = 0; i < m; i++) {
            for(int j = 0; j < n; j++) {
                if(grid[i][j] == '1' && !visited[i][j]) {
                    queue.offer(new int[]{i, j});
                    while(!queue.isEmpty()) {
                        int[] curr = queue.poll();
                        for(int k = 0; k < 4; k++) {
                            int r = curr[0] + dirs[k][0];
                            int c = curr[1] + dirs[k][1];
                            // 判断当前坐标的合法性。
                            if(r < 0 || r >= m || c <0 || c >= n || visited[r][c] || grid[r][c] == '0') {
                                continue;
                            }else{
                                visited[r][c] = true;
                                queue.offer(new int[]{r, c});
                            }
                        }
                    }
                    ans++;
                }
            }
        }

        return ans;
    }
}

方法三、并查集

时间复杂度: O(MN×α(MN))
空间复杂度:O(MN)是并查集需要的空间

对于所有的并查集的方法来说,包括两个方法:find(查询两个元素是否在一个集合里面)和union(如果两个元素符合一个集合,就进行合并)

定义parent数组,开始的时候每一个部分属于一个子集

/**
        3、并查集
     */
    class UnionFind {
        int count;
        int[] parent;
        int[] rank;

        public UnionFind(char[][] grid) {
            count = 0;
            int m = grid.length;
            int n = grid[0].length;
            parent = new int[m * n];
            rank = new int[m * n];
            for (int i = 0; i < m; ++i) {
                for (int j = 0; j < n; ++j) {
                    // 开始的时候,把每一个岛屿都看成是一个独立的子集。
                    if (grid[i][j] == '1') {
                        parent[i * n + j] = i * n + j;
                        ++count;
                    }
                    rank[i * n + j] = 0;
                }
            }
        }

        // 寻找当前位置的最最最祖先的节点,这样最后计算岛屿数量的时候,只要当前位置为1且当前位置的值没有发生变化,
        // 就是一个岛屿。
        public int find(int i) {
            if (parent[i] != i) parent[i] = find(parent[i]);
            return parent[i];
        }

        // 合并两个位置,谁rank比较大,谁是祖先,合并了count就要--,代表两个岛屿合并成了一个岛屿。
        public void union(int x, int y) {
            int rootx = find(x);
            int rooty = find(y);
            if (rootx != rooty) {
                if (rank[rootx] > rank[rooty]) {
                    parent[rooty] = rootx;
                } else if (rank[rootx] < rank[rooty]) {
                    parent[rootx] = rooty;
                } else {
                    parent[rooty] = rootx;
                    rank[rootx] += 1;
                }
                --count;
            }
        }

        public int getCount() {
            return count;
        }
    }

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

        int nr = grid.length;
        int nc = grid[0].length;
        int num_islands = 0;
        UnionFind uf = new UnionFind(grid);
        for (int r = 0; r < nr; ++r) {
            for (int c = 0; c < nc; ++c) {
                if (grid[r][c] == '1') {
                    grid[r][c] = '0';
                    // 分别遍历当前位置的上下左右,使用union进行合并。
                    if (r - 1 >= 0 && grid[r-1][c] == '1') {
                        uf.union(r * nc + c, (r-1) * nc + c);
                    }
                    if (r + 1 < nr && grid[r+1][c] == '1') {
                        uf.union(r * nc + c, (r+1) * nc + c);
                    }
                    if (c - 1 >= 0 && grid[r][c-1] == '1') {
                        uf.union(r * nc + c, r * nc + c - 1);
                    }
                    if (c + 1 < nc && grid[r][c+1] == '1') {
                        uf.union(r * nc + c, r * nc + c + 1);
                    }
                }
            }
        }

        return uf.getCount();
    }
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值