• 图-连通分量-DFS-并查集-695. 岛屿的最大面积


    2020-03-15 16:41:45

    问题描述:

    给定一个包含了一些 0 和 1的非空二维数组 grid , 一个 岛屿 是由四个方向 (水平或垂直) 的 1 (代表土地) 构成的组合。你可以假设二维矩阵的四个边缘都被水包围着。

    找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为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’。

    示例 2:

    [[0,0,0,0,0,0,0,0]]
    对于上面这个给定的矩阵, 返回 0。

    注意: 

    给定的矩阵grid 的长度和宽度都不超过 50。

    相似问题:

    749. 隔离病毒

    803. 打砖块

    问题求解:

    解法一:DFS

    使用DFS求解可以理解为将整张图中的连通分量进行染色,在染色过程中记录下当前染色的色块数量,最后取最大值即可。

    时间复杂度:O(mn)

        int[][] dirs = new int[][]{{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
        int color = 1;
        int area = 0;
        int res = 0;
        public int maxAreaOfIsland(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) {
                        area = 0;
                        dfs(grid, i, j, ++color);
                        res = Math.max(res, area);
                    }
                }
            }
            return res;
        }
        
        private void dfs(int[][] grid, int x, int y, int color) {
            int m = grid.length;
            int n = grid[0].length;
            grid[x][y] = color;
            area += 1;
            for (int[] dir : dirs) {
                int nx = x + dir[0];
                int ny = y + dir[1];
                if (nx < 0 || nx >= m || ny < 0 || ny >= n || grid[nx][ny] != 1) continue;
                dfs(grid, nx, ny, color);
            }
        }
    

    解法二:并查集

    采用并查集也可以解决这个问题,具体来说,对于每一个还未被连接的陆地,我们需要将其和它的四个邻居进行union,并且在union的时候需要维护这个集合的总的个数。最后返回森林中个数最大的即可。

    时间复杂度:O(mn) 

        int[][] dirs = new int[][]{{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
        public int maxAreaOfIsland(int[][] grid) {
            int res = 0;
            int m = grid.length;
            int n = grid[0].length;
            int[] parent = new int[m * n];
            int[] area = new int[m * n];
            for (int i = 0; i < m * n; i++) {
                parent[i] = i;
                area[i] = 1;
            }
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    int key = i * n + j;
                    if (grid[i][j] == 1 && parent[key] == key) {
                        for (int[] dir : dirs) {
                            int ni = i + dir[0];
                            int nj = j + dir[1];
                            if (ni < 0 || ni >= m || nj < 0 || nj >= n || grid[ni][nj] == 0) continue;
                            int nk = ni * n + nj;
                            union(parent, key, nk, area);
                        }
                    }
                }
            }
            for (int i = 0; i < m * n; i++) {
                if (grid[i / n][i % n] == 1 && parent[i] == i) res = Math.max(res, area[i]);
            }
            return res;
        }
        
        private int find(int[] parent, int i) {
            if (parent[i] != i) parent[i] = find(parent, parent[i]);
            return parent[i];
        }
        
        private boolean union(int[] parent, int i, int j, int[] area) {
            int pi = find(parent, i);
            int pj = find(parent, j);
            if (pi == pj) return false;
            parent[pi] = pj;
            area[pj] += area[pi];
            return true;
        }
    

     

    803. 打砖块

    问题描述:

    我们有一组包含1和0的网格;其中1表示砖块。 当且仅当一块砖直接连接到网格的顶部,或者它至少有一块相邻(4 个方向之一)砖块不会掉落时,它才不会落下。

    我们会依次消除一些砖块。每当我们消除 (i, j) 位置时, 对应位置的砖块(若存在)会消失,然后其他的砖块可能因为这个消除而落下。

    返回一个数组表示每次消除操作对应落下的砖块数目。

    示例 1:

    输入:
    grid = [[1,0,0,0],[1,1,1,0]]
    hits = [[1,0]]
    输出: [2]
    解释: 
    如果我们消除(1, 0)位置的砖块, 在(1, 1) 和(1, 2) 的砖块会落下。所以我们应该返回2。

    示例 2:

    输入:
    grid = [[1,0,0,0],[1,1,0,0]]
    hits = [[1,1],[1,0]]
    输出:[0,0]
    解释:
    当我们消除(1, 0)的砖块时,(1, 1)的砖块已经由于上一步消除而消失了。所以每次消除操作不会造成砖块落下。注意(1, 0)砖块不会记作落下的砖块。

    注意:

    网格的行数和列数的范围是[1, 200]。
    消除的数字不会超过网格的区域。
    可以保证每次的消除都不相同,并且位于网格的内部。
    一个消除的位置可能没有砖块,如果这样的话,就不会有砖块落下。

    问题求解:

    解法一:DFS

    我们可以使用dfs进行模拟,每次击打砖块后,对其四个邻居进行判断,看其是否会掉落,如果掉落,则将其连通块统一设置为0。

    时间复杂度:O(mn*len(hits))

        // leetcode AC; lintcode Fail
        int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
        int cnt = 0;
        public int[] hitBricks(int[][] grid, int[][] hits) {
            int m = grid.length;
            int n = grid[0].length;
            int k = hits.length;
            int[] res = new int[k];
            for (int i = 0; i < k; i++) {
                int[] hit = hits[i];
                int x = hit[0];
                int y = hit[1];
                if (grid[x][y] == 0) continue;
                grid[x][y] = 0;
                cnt = 0;
                for (int[] dir : dirs) {
                    int nx = x + dir[0];
                    int ny = y + dir[1];
                    if (nx < 0 || nx >= m || ny < 0 || ny >= n || grid[nx][ny] == 0) continue;
                    if (is_fall(grid, nx, ny, new int[m][n])) hit_down(grid, nx, ny);
                }
                res[i] = cnt;
            }
            return res;
        }
        
        private boolean is_fall(int[][] grid, int x, int y, int[][] used) {
            int m = grid.length;
            int n = grid[0].length;
            if (x == 0) return false;
            used[x][y] = 1;
            for (int[] dir : dirs) {
                int nx = x + dir[0];
                int ny = y + dir[1];
                if (nx < 0 || nx >= m || ny < 0 || ny >= n || grid[nx][ny] == 0 || used[nx][ny] == 1) continue;
                if (!is_fall(grid, nx, ny, used)) return false;
            }
            return true;
        }
        
        private void hit_down(int[][] grid, int x, int y) {
            int m = grid.length;
            int n = grid[0].length;
            grid[x][y] = 0;
            cnt += 1;
            for (int[] dir : dirs) {
                int nx = x + dir[0];
                int ny = y + dir[1];
                if (nx < 0 || nx >= m || ny < 0 || ny >= n || grid[nx][ny] == 0) continue;
                hit_down(grid, nx, ny);
            }
        }
    

      

    解法二:并查集

    上述的DFS模拟的算法在lintcode里无法通过,显示递归深度过多。

    本题也可以使用并查集求解,主要要考虑到的是如何union:将敲砖块转变成添加砖块,从头开始敲砖块等价于从末尾开始添加砖块,这样就可以使用union操作了。另外,我们还需要一个节点表示所有连接到顶部的集合。

    时间复杂度:O(mn * len(hits))

        int[] parent;
        int[] size;
        int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
        public int[] hitBricks(int[][] grid, int[][] hits) {
            int[] res = new int[hits.length];
            int m = grid.length;
            int n = grid[0].length;
            parent = new int[m * n + 1];
            size = new int[m * n + 1];
            for (int i = 0; i <= m * n; i++) {
                parent[i] = i;
                size[i] = 1;
            }
            int[][] after = new int[m][n];
            for (int i = 0; i < m; i++) after[i] = Arrays.copyOf(grid[i], n);
            for (int[] hit : hits) {
                int x = hit[0];
                int y = hit[1];
                after[x][y] = 0;
            }
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    if (after[i][j] == 1) {
                        for (int[] dir : dirs) {
                            int ni = i + dir[0];
                            int nj = j + dir[1];
                            if (ni < 0 || ni >= m || nj < 0 || nj >= n || after[ni][nj] == 0) continue;
                            union(i * n + j, ni * n + nj);
                         }
                        if (i == 0) union(i * n + j, n * m);
                    }
                }
            }
            for (int i = hits.length - 1; i >= 0; i--) {
                int[] brick = hits[i];
                int x = brick[0];
                int y = brick[1];
                if (grid[x][y] == 0) continue;
                after[x][y] = 1;
                int prev = size[find(m * n)];
                if (x == 0) union(x * n + y, m * n);
                for (int[] dir : dirs) {
                    int nx = x + dir[0];
                    int ny = y + dir[1];
                    if (nx < 0 || nx >= m || ny < 0 || ny >= n || after[nx][ny] == 0) continue;
                    union(x * n + y, nx * n + ny);
                }
                res[i] = Math.max(0, size[find(m * n)] - prev - 1);
            }
            return res;
        }
        
        private int find(int i) {
            if (parent[i] != i) parent[i] = find(parent[i]);
            return parent[i];
        }
        
        private boolean union(int i, int j) {
            int pi = find(i);
            int pj = find(j);
            if (pi == pj) return false;
            parent[pi] = pj;
            size[pj] += size[pi];
            return true;
        }
    

      

  • 相关阅读:
    链表及其各种函数操作的实现方法
    插入排序
    欧几里得算法
    Cookie和Session
    RestTemplate 中文乱码
    java8 按两个属性分组,并返回扁平List; stream排序
    Spring 读取资源
    linux搭建Git
    IDEA 快捷键
    Linux常用命令
  • 原文地址:https://www.cnblogs.com/hyserendipity/p/12498427.html
Copyright © 2020-2023  润新知