1020. 飞地的数量 : 「并查集 + DFS」&「多源 BFS」

简介: 1020. 飞地的数量 : 「并查集 + DFS」&「多源 BFS」

网络异常,图片无法展示
|


题目描述



这是 LeetCode 上的 1020. 飞地的数量 ,难度为 中等


Tag : 「DFS」、「并查集」


给你一个大小为 m x nmxn 的二进制矩阵 gridgrid ,其中 00 表示一个海洋单元格、11 表示一个陆地单元格。


一次 移动 是指从一个陆地单元格走到另一个相邻(上、下、左、右)的陆地单元格或跨过 gridgrid 的边界。


返回网格中 无法 在任意次数的移动中离开网格边界的陆地单元格的数量。


示例 1:


网络异常,图片无法展示
|


输入:grid = [[0,0,0,0],[1,0,1,0],[0,1,1,0],[0,0,0,0]]
输出:3
解释:有三个 1 被 0 包围。一个 1 没有被包围,因为它在边界上。
复制代码


示例 2:


网络异常,图片无法展示
|


输入:grid = [[0,1,1,0],[0,0,1,0],[0,0,1,0],[0,0,0,0]]
输出:0
解释:所有 1 都在边界上或可以到达边界。
复制代码


提示:


  • m == grid.lengthm==grid.length
  • n == grid[i].lengthn==grid[i].length
  • 1 <= m, n <= 5001<=m,n<=500
  • grid[i][j]grid[i][j] 的值为 0011


并查集 + DFS



根据题目定义,我们知道需要统计所有不和「边缘陆地」相连通的「普通陆地」数量。


我们可以用「并查集」来维护连通块,使用 DFS 对所有「边缘陆地连通块」进行标记(设定编号为 00 的超级源点,对于所有的「边缘陆地连通块」,将其与超级源点联通)。


具体的,我们按照如下流程进行处理:


  • 初始化并查集:起始让每个单元格独立作为一个连通块;
  • 使用 DFS 标记所有「边缘陆地连通块」:从位于边缘的「边缘陆地」进行出发,将其所在连通块与超级源点 00 进行联通标记(同时为了确保复杂度,我们在进行 DFS 前需要先检查当前陆地与超级源点的联通关系,如果已联通,说明当前陆地棣属于之前的某个连通块,已被整体标记过,进行跳过即可);
  • 统计答案:遍历整个棋盘,统计所有不与超级源点 00 联通的陆地数量。


一些细节:由于我们人为规定了超级源点编号为 00,同时棋盘下标从 00 开始,因此对某个点 (x, y)(x,y) 的编号,我们需要增加一个偏移量,例如 idx = x * n + y + 1idx=xn+y+1


代码:


class Solution {
    int N = 550;
    int[] p = new int[N * N];
    int m, n;
    int[][] g;
    int find(int x) {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }
    boolean query(int a, int b) {
        return find(a) == find(b);
    }
    void union(int a, int b) {
        p[find(a)] = find(b);
    }
    public int numEnclaves(int[][] grid) {
        g = grid;
        m = g.length; n = g[0].length;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                p[getIdx(i, j)] = getIdx(i, j);
            }
        }
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (i == 0 || j == 0 || i == m - 1 || j == n - 1) {
                    if (g[i][j] != 1 || query(getIdx(i, j), 0)) continue;
                    dfs(i, j);
                }
            }
        }
        int ans = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (g[i][j] == 1 && !query(getIdx(i, j), 0)) ans++;
            }
        }
        return ans;
    }
    int[][] dirs = new int[][]{{1,0},{-1,0},{0,1},{0,-1}};
    void dfs(int x, int y) {
        union(getIdx(x, y), 0);
        for (int[] d : dirs) {
            int nx = x + d[0], ny = y + d[1];
            if (nx < 0 || nx >= m || ny < 0 || ny >= n) continue;
            if (g[nx][ny] != 1 || query(getIdx(nx, ny), 0)) continue;
            dfs(nx, ny);
        }
    }
    int getIdx(int x, int y) {
        return x * n + y + 1;
    }
}
复制代码


  • 时间复杂度:初始化并查集复杂度为 O(m * n)O(mn);使用 DFS 对边缘陆地连通块进行标记复杂度为 O(m * n)O(mn);统计答案复杂度为 O(m * n)O(mn)。整体复杂度为 O(m * n)O(mn)
  • 空间复杂度:O(m * n)O(mn)


多源 BFS



也可以使用「多源 BFS」进行求解。


将所有「边缘陆地」看做与超级源点相连,起始将所有「边缘陆地」进行入队(等价于只将超级源点入队,然后取出超级源点并进行拓展)。


然后是常规的 BFS 过程,所有能够出队/入队的陆地格子,都代表与「边缘陆地」联通,都不属于「飞地」,对其进行标记。


最后遍历整个棋盘,统计所有未被标记的「陆地」格子数量即是答案。


不熟悉「多源 BFS」的同学可以看前置 🧀 :多源 BFS 入门


代码:


class Solution {
    public int numEnclaves(int[][] g) {
        int m = g.length, n = g[0].length;
        boolean[][] vis = new boolean[m][n];
        Deque<int[]> d = new ArrayDeque<>();
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (i == 0 || j == 0 || i == m - 1 || j == n - 1) {
                    if (g[i][j] == 0) continue;
                    vis[i][j] = true;
                    d.addLast(new int[]{i, j});
                }
            }
        }
        int[][] dirs = new int[][]{{1,0},{-1,0},{0,1},{0,-1}};
        while (!d.isEmpty()) {
            int[] poll = d.pollFirst();
            int x = poll[0], y = poll[1];
            for (int[] di : dirs) {
                int nx = x + di[0], ny = y + di[1];
                if (nx < 0 || nx >= m || ny < 0 || ny >= n) continue;
                if (g[nx][ny] != 1) continue;
                if (vis[nx][ny]) continue;
                vis[nx][ny] = true;
                d.addLast(new int[]{nx, ny});
            }
        }
        int ans = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (g[i][j] == 1 && !vis[i][j]) ans++;
            }
        }
        return ans;
    }
}
复制代码


  • 时间复杂度:O(m * n)O(mn)
  • 空间复杂度:O(m * n)O(mn)


最后



这是我们「刷穿 LeetCode」系列文章的第 No.1011 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。


在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。


为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour…


在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

相关文章
|
7月前
|
机器学习/深度学习 测试技术
【深度优先搜索】【树】【有向图】【推荐】685. 冗余连接 II
【深度优先搜索】【树】【有向图】【推荐】685. 冗余连接 II
|
6月前
|
算法 Java
Java数据结构与算法:图算法之深度优先搜索(DFS)
Java数据结构与算法:图算法之深度优先搜索(DFS)
|
6月前
|
搜索推荐 Java
单源最短路(只有一个起点)bfs,多源BFS,目录力扣675.为高尔夫比赛砍树,多源最短路问题:力扣542.01矩阵力扣1020.飞地的数量
单源最短路(只有一个起点)bfs,多源BFS,目录力扣675.为高尔夫比赛砍树,多源最短路问题:力扣542.01矩阵力扣1020.飞地的数量
|
7月前
|
机器学习/深度学习 算法 测试技术
【单源最短路 图论】882. 细分图中的可到达节点
【单源最短路 图论】882. 细分图中的可到达节点
|
7月前
|
人工智能 算法 BI
【广度优先搜索】【二分图】【并集查找】2493. 将节点分成尽可能多的组
【广度优先搜索】【二分图】【并集查找】2493. 将节点分成尽可能多的组
|
7月前
|
人工智能 算法 BI
【图论】【 割边】【C++算法】1192. 查找集群内的关键连接
【图论】【 割边】【C++算法】1192. 查找集群内的关键连接
|
7月前
|
人工智能 算法 BI
【树】【因子数】【数论】【深度优先搜索】2440. 创建价值相同的连通块
【树】【因子数】【数论】【深度优先搜索】2440. 创建价值相同的连通块
多源BFS+最小步数模型+双端队列广搜
多源BFS+最小步数模型+双端队列广搜
43 0
|
存储 算法 索引
【霍罗维兹数据结构】GRAPH 图 | 基本图运算 DFS&BFS | 最小代价生成树
【霍罗维兹数据结构】GRAPH 图 | 基本图运算 DFS&BFS | 最小代价生成树
122 0
|
定位技术
DFS:迷宫解的方案数
DFS:迷宫解的方案数