两个题解法其实是一致的,当多练一遍
定义
英文是(union&find)是一种树形的数据结构,用于处理一些不交集的合并和查询问题
find
确定元素属于哪一个子集,它可以被用来确定两个元素是否属于同一个子集
union
将两个子集合并成一个集合
路径压缩
寻找根节点,如果元素过多,就会比较复杂,树形层次太多。路径压缩就是:把祖先节点作为所有子孙节点的parent,这样祖先节点和所有子孙节点都是直连的,find操作就变成O(1)的时间复杂度。
按秩合并(rank合并)
秩表示树的高度,在合并的时候,总是将较小秩的树根指向较大的树根,这样合并后的数秩就不会变大。
public class QuickUnionF {
private int[] roots;
/**
* 构造新的并查集
* @param N 并查集的长度
*/
public QuickUnionF(int N) {
this.roots = new int[N];
for (int i = 0; i < N; i++) {
// 表示自己指向自己
roots[i] = i;
}
}
/**
* 查看元素属于哪个集合,路径压缩版
* @param element 元素
* @return 元素所在集合
*/
public int find(int element) {
// 先找到字集所在的集合
int root = element;
// 只要节点没指向自己,继续遍历,直接找到所属集合为止
while (root != roots[root]) {
//把当前的所属集合取出来
root = roots[element];
}
//进行路径压缩
while(root != roots[element]) {
roots[element] = root;
}
return roots[element];
}
/**
* 判断两个元素是否在同一个集合
*/
public boolean isConnectd(int firstElement, int secondElement) {
return find(firstElement) == find(secondElement);
}
/**
* 两个字集合合并
*/
public void unionElements(int firstElement, int secondElement) {
int firstUnion = find(firstElement);
int secondUnion = find(secondElement);
if (firstUnion != secondUnion) {
roots[firstUnion] = secondUnion;
}
}
一个岛被水包围,并且它是通过水平方向或者垂直方向相邻的陆地连接而成。你可以假设网格的四个边均被水包围。
示例:
11000
11000
00100
00011
输出:3
遍历所有节点,如果节点为1,count++,然后将周围的是1的节点染色改为0,最后得到count的值即岛屿的个数。
DFS
public static int numsIsLands(char[][] grid) {
int x = grid.length;
int y = grid[0].length;
int count = 0;
for (int i = 0; i < x; i++) {
for (int j = 0; j < y; j++) {
if (grid[i][j] == '1') {
// count++
count++;
// 将周边的1给换成0
dfsFloodFill(grid, i, j);
}
}
}
return count;
}
private static void dfsFloodFill(char[][] grid, int i, int j) {
int x = grid.length;
int y = grid[0].length;
//边界及0不处理
if (i < 0 || j < 0 || i >=x || j >= y || grid[i][j] == '0') return;
// 染色为'0'
grid[i][j] = '0';
dfsFloodFill(grid, i-1, j);
dfsFloodFill(grid, i, j-1);
dfsFloodFill(grid, i+1,j);
dfsFloodFill(grid, i, j+1);
}
BFS
遍历逻辑跟DFS一样,染色的逻辑使用的广度优先搜索,借助LinkedList数据结构,将相邻节点放到暂存list中,挨个取出进行判断,直到周边节点为0为止。
public static int numsIsLands1(char[][] grid) {
int x = grid.length;
int y = grid[0].length;
int count = 0;
for (int i = 0; i < x; i++) {
for (int j = 0; j < y; j++) {
if (grid[i][j] == '1') {
// count++
count++;
// 将周边的1给换成0
bfsFloodFill(grid, i, j);
}
}
}
return count;
}
private static void bfsFloodFill(char[][] grid, int i, int j) {
LinkedList chars = new LinkedList<>();
int x = grid.length;
int y = grid[0].length;
chars.add(new int[]{i, j});
while (!chars.isEmpty()) {
int[] cur = chars.remove();
int a = cur[0];
int b = cur[1];
if (a < 0 || b < 0 || a >= x || b >= y || grid[a][b] == '0') continue;
grid[a][b] = '0';
chars.add(new int[]{a - 1, b});
chars.add(new int[]{a + 1, b});
chars.add(new int[]{a, b - 1});
chars.add(new int[]{a, b + 1});
}
}
初始化并查集,把所有为’1’的节点都指向自己
把相邻的’1’进行合并
遍历查看有多少个不同的集合
class UnionFind {
int count;
int[] parent;
int[] rank;
public UnionFind(char[][] grid) {
count = 0;
int x = grid.length;
int y = grid[0].length;
parent = new int[x * y];
rank = new int[x * y];
// 初始化并查集
for (int i = 0; i < x; i++) {
for (int j = 0; j < y; j++) {
int index = i * x + j;
if (grid[i][j] == '1') {
parent[index] = index;
count++;
}
rank[index] = 0;
}
}
}
public int find(int index) {
if (index != parent[index])
parent[index] = find(parent[index]);
return parent[index];
}
public void union(int x, int y) {
int rootX = find(x);
int rootY = find(y);
// rank合并优化
if (rootX != rootY) {
if (rank[rootX] > rank[rootY]) {
parent[rootY] = rootX;
}
if (rank[rootX] < rank[rootY]) {
parent[rootX] = rootY;
}
if (rank[rootX] == rank[rootY]) {
parent[rootX] = rootY;
rank[rootY] += 1;
}
--count;
}
}
public int getCount() {
return count;
}
}
public int numsIsLands(char[][] grid) {
if (grid == null || grid.length == 0) return 0;
int x = grid.length;
int y = grid[0].length;
// 1. 初始化并查集
UnionFind unionFind = new UnionFind(grid);
// 2. 遍历合并为1的节点
for (int i = 0; i < x; i++) {
for (int j = 0; j < y; j++) {
if (grid[i][j] == '1') {
grid[i][j] = '0';
// 合并上下左右的为1的节点(上下是指坐标的上下)
// 合并上一个节点
if (j - 1 >= 0 && grid[i][j - 1] == '1') {
unionFind.union(i * x + j, i * x + j - 1);
}
// 合并下一个节点
if (j + 1 < y && grid[i][j + 1] == '1') {
unionFind.union(i * x + j, i * x + j + 1);
}
// 合并左边的节点
if (i - 1 >= 0 && grid[i - 1][j] == '1') {
unionFind.union(i * x + j, (i - 1) * x + j);
}
//合并右边的节点
if (i + 1 < x && grid[i + 1][j] =='1') {
unionFind.union(i * x + j, (i + 1) * x + j);
}
}
}
}
// 3. 获取不同的集合个数
return unionFind.getCount();
}
给定一个N*N的矩阵M,表示班级中学生之间的朋友关系。如果M[i][j]=1,表示已知第i个和第j个学生之间互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。
示例:
输入:
[[1,1,0].
[1,1,0],
[0,0,1]]
输出:2
dfs遍历染色
int x;
int y;
public int numsFriendCycles(int[][] grid) {
x = grid.length;
y = grid[0].length;
int count = 0;
for (int i = 0; i < x; i++) {
for (int j = 0; j < y; j++) {
if (grid[i][j] == 1) {
count++;
//dfs染色
dfsFloodFill(grid, i, j);
}
}
}
return count;
}
private void dfsFloodFill(int[][] grid, int i, int j) {
if (i < 0 || j < 0 || i >= x || j >=y || grid[i][j] == 0) return;
grid[i][j] = 0;
dfsFloodFill(grid, i - 1, j);
dfsFloodFill(grid, i + 1, j);
dfsFloodFill(grid, i, j - 1);
dfsFloodFill(grid, i, j + 1);
}
bfs
int x;
int y;
public int numsFriendCycles1(int[][] grid) {
x = grid.length;
y = grid[0].length;
int count = 0;
for (int i = 0; i < x; i++) {
for (int j = 0; j < y; j++) {
if (grid[i][j] == 1) {
count++;
//bfs染色
bfsFloodFill(grid, i, j);
}
}
}
return count;
}
// 这里面的i,j 和循环里面的a和b不能混淆
private void bfsFloodFill(int[][] grid, int i, int j) {
LinkedList bfsList = new LinkedList<>();
bfsList.add(new int[]{i, j});
while (!bfsList.isEmpty()) {
int[] item = bfsList.remove();
int a = item[0];
int b = item[1];
if (a < 0 || b < 0 || a >= x || b >=y || grid[a][b] == 0) continue;
grid[a][b] = 0;
// 将周边节点加入辅助Queue
bfsList.add(new int[]{a-1, b});
bfsList.add(new int[]{a + 1, b});
bfsList.add(new int[]{a, b - 1});
bfsList.add(new int[] {a, b + 1});
}
}
经过分析,可以看做一个并查集的问题,自己指向自己,然后合并相邻的节点,最后看有多少个集合即可。
先将所有1的节点初始化
遍历,如果当前节点为1,则将周边的所有1都合并到一个集合
得出总的集合数
class FriendUnionF{
int parent[];
int rank[];
int count = 0;
public FriendUnionF(int[][] grid) {
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++) {
int index = i * m + j;
if (grid[i][j] == 1) {
parent[index] = index;
count ++;
}
rank[index] = 0;
}
}
}
public int find(int m) {
if (parent[m] != m) {
parent[m] = find(parent[m]);
}
return parent[m];
}
// 合并时采用按rank合并优化
public void union(int m, int n) {
// 分别查找root
int rootM = find(m);
int rootN = find(n);
if (rootM != rootN) {
if (rank[rootM] > rank[rootN]) {
parent[rootN] = rootM;
}else if (rank[rootM] < rank[rootN]) {
parent[rootM] = rootN;
}else{
parent[rootN] = rootM;
rank[rootM] += 1;
}
--count;
}
}
}
public int numsFriendCycles(int[][] grid) {
// 1. 先将所有1的节点初始化
FriendUnionF friendUnionF = new FriendUnionF(grid);
int m = grid.length;
int n = grid[0].length;
// 遍历,如果当前节点为1,则将周边的所有1都合并到一个集合
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
grid[i][j] = 0;
int index = i * m + j;
//将周边的1合并
if (i - 1 >= 0 && grid[i - 1][j] == 1) {
// 注意第二个参数简化的时候不要出错,第一遍就因为这个参数简化导致出现问题
friendUnionF.union(index, (i - 1) * m + j);
}
if (i + 1 < m && grid[i + 1][j] == 1) {
friendUnionF.union(index, (i + 1) * m + j);
}
if (j - 1 >= 0 && grid[i][j - 1] == 1) {
friendUnionF.union(index, i * m + j - 1);
}
if (j + 1< n && grid[i][j + 1] == 1) {
friendUnionF.union(index, i * m + j + 1);
}
}
}
}
return friendUnionF.count;
}