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 原题链接和其他优选题解。

相关文章
mAP@0.5与mAP@0.50.95的含义,YOLO
mAP@0.5与mAP@0.50.95的含义,YOLO
2038 0
|
Android开发 芯片 开发者
adb 查看安卓手机 CPU 类型(armeabi、armeabi-v7a、arm64-v8a ...)
adb 查看安卓手机 CPU 类型(armeabi、armeabi-v7a、arm64-v8a ...)
3975 0
|
消息中间件 缓存 前端开发
COLA架构 入门
COLA架构 入门
4080 0
|
消息中间件 存储 监控
五分钟快速了解Airflow工作流
简介 Airflow是一个以编程方式创作、调度和监控工作流的平台。 使用 Airflow 将工作流创作为有向无环图(DAG)任务。 Airflow 调度程序按照你指定的依赖项在一组workers上执行您的任务。同时,Airflow拥有丰富的命令行实用程序使得在DAG上进行复杂的诊断变得轻而易举。并且提供了丰富的用户界面使可视化生产中运行的工作流、监控进度和需要排查问题时变得非常容易。 当工作流被定义为代码时,它们变得更易于维护、可版本化、可测试和协作。
|
Java Spring
使用Spring initializr快速创建一个springboot项目
Spring initializr快速创建一个springboot 改服务器端口号
578 0
使用Spring initializr快速创建一个springboot项目
|
jenkins Java Devops
【DevOps】Idea 集成 jenkins 插件
【DevOps】Idea 集成 jenkins 插件
2036 0
【DevOps】Idea 集成 jenkins 插件
|
缓存 JavaScript Windows
windows环境下NPM / NodeJS的安装配置
npm(node package manager):nodejs的包管理器,用于node插件管理(包括安装、卸载、管理依赖等) 本文主要讲解如何搭建npm环境
8612 0
windows环境下NPM / NodeJS的安装配置
解决Vscode使用LeetCode报错Failed to test the solution. Please open the output channel for details.
本文提供了解决在VScode中使用LeetCode插件时遇到“Failed to test the solution. Please open the output channel for details.”错误的方法,主要是通过修改setting.json文件中的输出文件夹配置来解决。
1443 1
|
缓存 监控 前端开发
java简历2年经验编写教程+面试题
是花了我很多天的心思,用心打造出来的Java简历分析模板,适合新手包装成有一点工作年限(1-2年),但又不会太老手的简历;让你的简历做得跟别人不一样;
4597 0
|
机器学习/深度学习
YOLOv5改进 | 注意力篇 | ACmix自注意力与卷积混合模型(提高FPS+检测效率)
YOLOv5改进 | 注意力篇 | ACmix自注意力与卷积混合模型(提高FPS+检测效率)
379 0