【LeetCode】﹝并查集ி﹞连通分量个数(套用模板一直爽)

文章目录

  • 【LeetCode】﹝并查集ி﹞连通分量个数(套用模板一直爽)
    • 模板(使用路径压缩的加权quick-union算法)
    • 连通网络的操作次数★★
    • 省份数量★★
    • 岛屿数量★★
    • 由斜杠划分区域★★
    • 打砖块★★★
    • 保证图可完全遍历★★★

模板(使用路径压缩的加权quick-union算法)

详细并查集的知识见往期博客 高级数据结构(Ⅰ)并查集(Union-Find)

    class UF{int N;int count;int[] id;int[] sz;UF(int N){this.N = N;count = N;id = new int[N];sz = new int[N];for(int i = 0; i < N; i++) {id[i] = i;sz[i] = 1;}}public int getCount() {return count;}public void union(int p, int q) {int pRoot = find(p);int qRoot = find(q);if(pRoot != qRoot) {if(sz[pRoot] < sz[qRoot]) {id[pRoot] = id[qRoot];sz[qRoot] += sz[pRoot];}else {id[qRoot] = id[pRoot];sz[pRoot] += sz[qRoot];}count--;}}private int find(int p) {if(p == id[p]) return p;id[p] = find(id[p]);return id[p];}}

连通网络的操作次数★★

1319. 连通网络的操作次数

题目】用以太网线缆将 n 台计算机连接成一个网络,计算机的编号从 0n-1。线缆用 connections 表示,其中 connections[i] = [a, b] 连接了计算机 ab

网络中的任何一台计算机都可以通过网络直接或者间接访问同一个网络中其他任意一台计算机。

给你这个计算机网络的初始布线 connections,你可以拔开任意两台直连计算机之间的线缆,并用它连接一对未直连的计算机。请你计算并返回使所有计算机都连通所需的最少操作次数。如果不可能,则返回 -1

提示

  • 1 <= n <= 10^5
  • 1 <= connections.length <= min(n*(n-1)/2, 10^5)
  • connections[i].length == 2
  • 0 <= connections[i][0], connections[i][1] < n
  • connections[i][0] != connections[i][1]
  • 没有重复的连接。
  • 两台计算机不会通过多条线缆连接。

示例

示例1:
输入:n = 4, connections = [[0,1],[0,2],[1,2]]
输出:1
解释:拔下计算机 1 和 2 之间的线缆,并将它插到计算机 1 和 3 上。

示例2:
输入:n = 6, connections = [[0,1],[0,2],[0,3],[1,2]]
输出:-1
解释:线缆数量不足。

解题思路

利用并查集求出多余的缆线数量和连通分量的个数(java耗时6ms)

class Solution {public int makeConnected(int n, int[][] connections) {UF uf = new UF(n);int ress = 0;for (int[] connection : connections) {int t = uf.union(connection[0], connection[1]);if (t == -1) {ress++;}}return ress >= uf.getCount() - 1 ? uf.getCount() - 1 : -1;}class UF {int N;int count;int[] id;int[] sz;private UF (int n) {N = n;count = n;id = new int[N];sz = new int[N];for (int i = 0; i < N; i++) {id[i] = i;sz[i] = 1;}}public int getCount () {return count;}public int union (int p, int q) {int pRoot = find(p);int qRoot = find(q);if (pRoot != qRoot) {if (sz[pRoot] < sz[qRoot]) {id[pRoot] = qRoot;sz[qRoot] += sz[pRoot];} else {id[qRoot] = pRoot;sz[pRoot] += sz[qRoot];}count--;return 1;} else {return -1;}}private int find (int p) {if (p == id[p]) {return p;}id[p] = find(id[p]);return id[p];}}
}

模板因题测试而异,如测试数据量较大,适合用上述模板,若测试数据量较小,使用如下简单版本的并查集

(java耗时3ms)

class Solution {public int makeConnected(int n, int[][] connections) {int[] id = new int[n];for (int i = 0; i < n; i++) {id[i] = i;}int cons = n, ress = 0;for (int[] e : connections) {int p1 = find(id, e[0]);int p2 = find(id, e[1]);if (p1 == p2) {ress++;} else {cons--;id[p1] = p2;}}return ress + 1 >= cons ? cons - 1 : -1;}private int find(int[] id, int p) {if (p == id[p]) return p;id[p] = find(id, id[p]);return id[p];}
}

省份数量★★

547. 省份数量

题目】有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。

省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。

给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。

返回矩阵中 省份 的数量。

提示

  • 1 <= n <= 200
  • n == isConnected.length
  • n == isConnected[i].length
  • isConnected[i][j] 为 1 或 0
  • isConnected[i][i] == 1
  • isConnected[i][j] == isConnected[j][i]

示例

输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
输出:2
-----------------------------------------------------
输入:isConnected = [[1,0,0],[0,1,0],[0,0,1]]
输出:3

解题思路

class Solution {int[] id;int count = 0;public int findCircleNum(int[][] M) {if (M == null || M.length == 0 || M[0].length == 0) return 0;count = M.length;id = new int[M.length];for (int i = 0; i < M.length; i++) id[i] = i;for (int i = 0; i < M.length; i++) {for (int j = 0; j < M.length; j++) {if (M[i][j] == 1) union(i, j);   }}return count;}private int find(int p) {if (p == id[p]) return p;id[p] = find(id[p]);return id[p];}private void union(int p, int q) {int pid = find(p);int qid = find(q);if (pid == qid) return;id[pid] = qid;count--;}
}

岛屿数量★★

200. 岛屿数量

题目】给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

提示

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 300
  • grid[i][j] 的值为 '0' 或 '1'

示例

输入:grid = [["1","1","1","1","0"],["1","1","0","1","0"],["1","1","0","0","0"],["0","0","0","0","0"]
]
输出:1
------------------------------------------------
输入:grid = [["1","1","0","0","0"],["1","1","0","0","0"],["0","0","1","0","0"],["0","0","0","1","1"]
]
输出:3

解题思路

方法一:并查集,只考虑右边和下边即可

class Solution {int[] id;public int numIslands(char[][] grid) {if (grid == null || grid.length == 0 || grid[0].length == 0) {return 0;}int R = grid.length, C = grid[0].length;int count = R * C;id = new int[R * C];for (int i = 0; i < id.length; i++) id[i] = i;for (int i = 0; i < R; i++) {for (int j = 0; j < C; j++) {char c = grid[i][j];int k = i * C + j;if (c == '0') {count--;} else {if (j < C - 1 && grid[i][j + 1] == '1') {count += union(k, k + 1);}if (i < R - 1 && grid[i + 1][j] == '1') {count += union(k, k + C);}}}}return count;}private int find(int p) {if (p == id[p]) {return p;}id[p] = find(id[p]);return id[p];}private int union(int p, int q) {int pRoot = find(p);int qRoot = find(q);if (pRoot != qRoot) {id[pRoot] = qRoot;return -1;} else {return 0;}}}

方法二:dfs,下沉与它连接的所有岛屿

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

由斜杠划分区域★★

959. 由斜杠划分区域

题目】在由 1 x 1 方格组成的 N x N 网格 grid 中,每个 1 x 1 方块由 /、\空格构成。这些字符会将方块划分为一些共边的区域。

(请注意,反斜杠字符是转义的,因此 \"\\" 表示。)。

返回区域的数目。

提示:

  1. 1 <= grid.length == grid[0].length <= 30
  2. grid[i][j]'/''\'、或 ' '

示例

输入:
[" /","  "
]
输出:1

输入:
["//","/ "
]
输出:3

解题思路

连接时需要考虑以下5种情况:

  • 字符为空格“ ”:此时需连接区域[1, 2, 3, 4]
  • 字符为斜杠"/":此时需连接区域[0, 3], [1, 2]
  • 字符为反斜杠"/":此时需连接区域[0, 1],[2, 3]
  • 考虑方格右端(如图中黄色区域所示)
  • 考虑方格下端(如图中青色区域所示)
class Solution {public int regionsBySlashes(String[] grid) {int N = grid.length;int size = 4 * N * N;UF uf = new UF(size);for (int i = 0; i < N; i++) {for (int j = 0; j < N; j++) {int k = i * N * 4 + j * 4;if (grid[i].charAt(j) == ' ') {uf.union(k, k + 1);uf.union(k + 1, k + 2);uf.union(k + 2, k + 3);} else if (grid[i].charAt(j) == '/') {uf.union(k, k + 3);uf.union(k + 1, k + 2);} else {uf.union(k, k + 1);uf.union(k + 2, k + 3);}if (j < N - 1) {uf.union(k + 1, k + 4 + 3);}if (i < N - 1) {uf.union(k + 2, k + N * 4);}}}return uf.getCount();}class UF {int N;int count;int[] id;int[] sz;UF (int N) {this.N = N;count = N;id = new int[N];sz = new int[N];for (int i = 0; i < N; i++) {id[i] = i;sz[i] = 1;}}public int getCount() {return count;}public void union (int p, int q) {int pRoot = find(p);int qRoot = find(q);if (pRoot != qRoot) {if (sz[pRoot] < sz[qRoot]) {id[pRoot] = id[qRoot];sz[qRoot] += sz[pRoot];} else {id[qRoot] = id[pRoot];sz[pRoot] += sz[qRoot];}count--;}}private int find (int p) {if (p == id[p]) return p;id[p] = find(id[p]);return id[p];}}
}

打砖块★★★

803. 打砖块

题目】有一个 m x n 的二元网格,其中 1 表示砖块,0 表示空白。砖块 稳定(不会掉落)的前提是:

  • 一块砖直接连接到网格的顶部,或者
  • 至少有一块相邻(4 个方向之一)砖块 稳定 不会掉落时

给你一个数组 hits ,这是需要依次消除砖块的位置。每当消除 hits[i] = (rowi, coli) 位置上的砖块时,对应位置的砖块(若存在)会消失,然后其他的砖块可能因为这一消除操作而掉落。一旦砖块掉落,它会立即从网格中消失(即,它不会落在其他稳定的砖块上)。

返回一个数组 result ,其中 result[i] 表示第 i 次消除操作对应掉落的砖块数目。

注意,消除可能指向是没有砖块的空白位置,如果发生这种情况,则没有砖块掉落。

提示

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 200
  • grid[i][j] 为 0 或 1
  • 1 <= hits.length <= 4 * 104
  • hits[i].length == 2
  • 0 <= xi <= m - 1
  • 0 <= yi <= n - 1
  • 所有 (xi, yi) 互不相同

示例

输入:grid = [[1,0,0,0],[1,1,1,0]], hits = [[1,0]]
输出:[2]
解释:
网格开始为:
[[1,0,0,0],[1,1,1,0]]
消除 (1,0) 处加粗的砖块,得到网格:
[[1,0,0,0][0,1,1,0]]
两个加粗的砖不再稳定,因为它们不再与顶部相连,也不再与另一个稳定的砖相邻,因此它们将掉落。得到网格:
[[1,0,0,0],[0,0,0,0]]
因此,结果为 [2] 。
--------------------------------------------------------------------------------
输入:grid = [[1,0,0,0],[1,1,0,0]], hits = [[1,1],[1,0]]
输出:[0,0]
解释:
网格开始为:
[[1,0,0,0],[1,1,0,0]]
消除 (1,1) 处加粗的砖块,得到网格:
[[1,0,0,0],[1,0,0,0]]
剩下的砖都很稳定,所以不会掉落。网格保持不变:
[[1,0,0,0], [1,0,0,0]]
接下来消除 (1,0) 处加粗的砖块,得到网格:
[[1,0,0,0],[0,0,0,0]]
剩下的砖块仍然是稳定的,所以不会有砖块掉落。
因此,结果为 [0,0] 。

解题思路

此题考察的是逆向思维,并查集和DFS都可以完成。在此我使用并查集。

这题只需求每次打击位置后掉落的砖块数目即可,因此可将矩阵中第一行(最顶端位置的砖块)汇聚到根节点,逆向求解是,先使用一个临时副本矩阵,对此进行操作,将打击掉后的砖块状态存入并查集中,然后逆向求解每次将砖块补齐后,前后根节点连接子树的大小差值。

详细见官方题解打砖块

class Solution {public int[] hitBricks(int[][] grid, int[][] hits) {if (grid == null || grid.length == 0 || grid[0].length == 0) {return new int[]{};}int R = grid.length, C = grid[0].length;int[][] copy = new int[R][C];for (int i = 0; i < R; i++) {for (int j = 0; j < C; j++) {copy[i][j] = grid[i][j];}}//1.击碎for (int[] hit : hits) {copy[hit[0]][hit[1]] = 0;}//2.建图连接int size = R * C;UF uf = new UF(size + 1);//顶层初始化for (int i = 0; i < C; i++) {if (copy[0][i] == 1) {uf.union(size, i);}}for (int i = 1; i < R; i++) {for (int j = 0; j < C; j++) {if (copy[i][j] == 1) {int con = i * C + j;//看上边if (copy[i - 1][j] == 1) {uf.union(con, con - C);}//看左边if (j > 0 && copy[i][j - 1] == 1) {uf.union(con, con - 1);}}}}//3.逆序补回砖块int[] dirs = {-1, 0, 1, 0, -1};int len = hits.length;int[] res = new int[len];for (int i = len - 1; i >= 0; i--) {int x = hits[i][0], y = hits[i][1];int loc = x * C + y;if (grid[x][y] == 0) {continue;}int origin = uf.getSize(size);if (x == 0) {uf.union(size, loc);}//观察四个方向,将连接的网格合并for (int k = 0; k < 4; k++) {int newx = x + dirs[k], newy = y + dirs[k + 1];if (newx < 0 || newx >= R || newy < 0 || newy >= C) {continue;}int newloc = newx * C + newy;if (copy[newx][newy] == 1) {uf.union(loc, newloc);}}int current = uf.getSize(size);res[i] = Math.max(0, current - origin - 1);//补上砖块copy[x][y] = 1;}return res;}private class UF {int[] id;int[] sz;UF (int n) {id = new int[n];sz = new int[n];for (int i = 0; i < n; i++) {id[i] = i;sz[i] = 1;}}public int getSize(int p) {int root = find(p);return sz[root];}public int find(int p) {if (p != id[p]) {id[p] = find(id[p]);}return id[p];}public void union(int p, int q) {int pRoot = find(p);int qRoot = find(q);if (pRoot == qRoot) return;id[qRoot] = pRoot;sz[pRoot] += sz[qRoot];}}
}

保证图可完全遍历★★★

1579. 保证图可完全遍历

题目AliceBob 共有一个无向图,其中包含 n 个节点和 3 种类型的边:

  • 类型 1:只能由 Alice 遍历。
  • 类型 2:只能由 Bob 遍历。
  • 类型 3:Alice 和 Bob 都可以遍历。

给你一个数组 edges ,其中 edges[i] = [typei, ui, vi] 表示节点 uivi 之间存在类型为 typei 的双向边。请你在保证图仍能够被 Alice和 Bob 完全遍历的前提下,找出可以删除的最大边数。如果从任何节点开始,Alice 和 Bob 都可以到达所有其他节点,则认为图是可以完全遍历的。

返回可以删除的最大边数,如果 Alice 和 Bob 无法完全遍历图,则返回 -1

提示

  • 1 <= n <= 10^5
  • 1 <= edges.length <= min(10^5, 3 * n * (n-1) / 2)
  • edges[i].length == 3
  • 1 <= edges[i][0] <= 3
  • 1 <= edges[i][1] < edges[i][2] <= n
  • 所有元组 (typei, ui, vi) 互不相同

示例

输入:n = 4, edges = [[3,1,2],[3,2,3],[1,1,3],[1,2,4],[1,1,2],[2,3,4]]
输出:2
解释:如果删除 [1,1,2] 和 [1,1,3] 这两条边,Alice 和 Bob 仍然可以完全遍历这个图。
再删除任何其他的边都无法保证图可以完全遍历。所以可以删除的最大边数是 2 。

解题思路

贪心思路,优先处理公共边,将公共边信息保存在并查集中,并计算多余的公共边(处于同一个连通分量);

然后对Alice和Bob分别处理(注意此时应在各自的并查集中执行操作),因为图可完全遍历等价于图中只存在一个连通分量,此时对其各自操作,边的顺序并不影响结果,累加多余的边(处于同一个连通分量)。

最后判断能否完全遍历即可。

class Solution {public int maxNumEdgesToRemove(int n, int[][] edges) {for (int[] edge : edges) {edge[1]--;edge[2]--;}UF ufa = new UF(n);UF ufb = new UF(n);int res = 0;//优先处理公共边for (int[] edge : edges) {if (edge[0] == 3) {res += ufa.union(edge[1], edge[2]);ufb.union(edge[1], edge[2]);}}//分别处理Alice和Bob的边for (int[] edge : edges) {if (edge[0] == 1) {res += ufa.union(edge[1], edge[2]);} else if (edge[0] == 2) {res += ufb.union(edge[1], edge[2]);}}return ufa.getCount() == 1 && ufb.getCount() == 1 ? res : -1;}private class UF {int count;   //连通分量个数int[] id;int[] sz;public UF(int n) {count = n;id = new int[n];sz = new int[n];for (int i = 0; i < n; i++) {id[i] = i;sz[i] = 1;}}public int getCount() {return count;}public int find(int p) {if (p != id[p]) {id[p] = find(id[p]);}return id[p];}public int union(int p, int q) {int pRoot = find(p);int qRoot = find(q);if (pRoot == qRoot) {return  1;}if (sz[pRoot] > sz[qRoot]) {id[qRoot] = pRoot;sz[pRoot] += sz[qRoot];} else {id[pRoot] = qRoot;sz[qRoot] += sz[pRoot];}count--;return 0;}}
}

【LeetCode】﹝并查集ி﹞连通分量个数(套用模板一直爽)相关推荐

  1. leetcode 并查集 547.省份数量/200岛屿数量

    [数据结构和算法笔记]用并查集求解等价关系_m0_52043808的博客-CSDN博客 模板: class UF { private:vector<int>father;//father数 ...

  2. 【CCCC】L2-013 红色警报 (25分),,并查集计算集合个数

    problem L2-013 红色警报 (25分) 战争中保持各个城市间的连通性非常重要.本题要求你编写一个报警程序,当失去一个城市导致国家被分裂为多个无法连通的区域时,就发出红色警报.注意:若该国本 ...

  3. LeetCode 323. 无向图中连通分量的数目(并查集)

    文章目录 1. 题目 2. 解题 1. 题目 给定编号从 0 到 n-1 的 n 个节点和一个无向边列表(每条边都是一对节点),请编写一个函数来计算无向图中连通分量的数目. 示例 1: 输入: n = ...

  4. sdut 1488 连通分量的个数(并查集)

    数据结构实验:连通分量个数 Time Limit: 1000MS Memory Limit: 65536KB Submit Statistic Discuss Problem Description ...

  5. Leetcode 323.无向连通图中的连通分量个数

    Time: 20190903 Type: Medium 题目描述 给定编号从 0 到 n-1 的 n 个节点和一个无向边列表(每条边都是一对节点),请编写一个函数来计算无向图中连通分量的数目. 示例 ...

  6. Leetcode(5)——遍历,并查集,回溯法和二分查找

    格式: 题号+题名+简单思路+code 遍历 深度优先遍历和广度优先遍历 很多dfs可以用Union Find解决 T130: 被围绕的区域 DFS的写法 func solve(board [][]b ...

  7. 并查集c++代码_[Leetcode 每日精选](本周主题-并查集) 547. 朋友圈

    题目难度: 中等 原题链接 今天继续来做并查集的问题, 这道题仍然比较基础, 而且也是个比较接近现实的问题了. 大家在我的公众号"每日精选算法题"中的聊天框中回复 并查集 就能看到 ...

  8. leetcode 765. 情侣牵手(并查集)

    N 对情侣坐在连续排列的 2N 个座位上,想要牵到对方的手. 计算最少交换座位的次数,以便每对情侣可以并肩坐在一起. 一次交换可选择任意两人,让他们站起来交换座位. 人和座位用 0 到 2N-1 的整 ...

  9. LintCode 434. 岛屿的个数II(并查集)

    文章目录 1. 题目 2. 解题 1. 题目 给定 n, m, 分别代表一个二维矩阵的行数和列数, 并给定一个大小为 k 的二元数组A. 初始二维矩阵全0. 二元数组A内的k个元素代表k次操作, 设第 ...

最新文章

  1. 【点云StatisticalOutlierFilter】python-pcl:去除离群点
  2. iOS 最新版 CocoaPods 的安装流程
  3. NC:噬菌体中无机硫辅助代谢基因的生态学研究
  4. 模板方法模式(Template Pattern)
  5. Opengl-基本概念-转换矩阵坐标系(最难理解的两章)
  6. 一天一点T-SQL:使用登录触发器进行安全管控
  7. MySQL 之Navicat Premium 12安装使用、pymysql模块使用、sql注入问题的产生与解决
  8. PHP的memory_limit
  9. drbd实现mysql地热备_heartheartbeat+drbd+mysql主库热备
  10. 如何才能成为编程高手?别人都不告诉你的东西,我来说给你听!
  11. 移动客户端UI设计指南
  12. Codeforces Round #356 (Div. 1) D. Bear and Chase 暴力
  13. JQuery表单验证插件EasyValidator
  14. Egg 2.20.0 发布,阿里开源的企业级 Node.js 框架
  15. Redis安装教程(各种坑)
  16. 地理坐标系转换工具,支持WGS84/GCJ02/BD09等常用坐标系互转
  17. python右对齐 数字,python怎么让数字右对齐
  18. 计算机音乐蜡笔小新,蜡笔小新背景音乐-原创
  19. 计算机硬盘加密的原理,一种计算机硬盘加密方法及装置
  20. android 自定view 网状结构图

热门文章

  1. 从零开始编写minecraft光影包(8)中级水面绘制 水下阴影与焦散
  2. 合同和协议的区别_协议书与合同书的区别
  3. 什么是DBMS,什么是数据库?
  4. 【docker 安装-环境初始化】
  5. 【职场必备知识】毕业留蓉政策与发展前景分析
  6. 7-12 编程实现两个分数相加
  7. 100道面试题,能否进蚂蚁看你了!
  8. excel设置斑马线
  9. 5G多卡聚合路由器在高速公路收费中的应用
  10. 未来的苹果和谷歌到底哪家强?