算法-并查集/DFS/BFS-朋友圈
1 题目概述
1.1 题目出处
https://leetcode-cn.com/problems/friend-circles/
1.2 题目描述
班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。
给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。
示例 1:
输入:
[[1,1,0],
[1,1,0],
[0,0,1]]
输出: 2
说明:已知学生0和学生1互为朋友,他们在一个朋友圈。
第2个学生自己在一个朋友圈。所以返回2。
示例 2:
输入:
[[1,1,0],
[1,1,1],
[0,1,1]]
输出: 1
说明:已知学生0和学生1互为朋友,学生1和学生2互为朋友,所以学生0和学生2也是朋友,所以他们三个在一个朋友圈,返回1。
注意:
- N 在[1,200]的范围内。
- 对于所有学生,有M[i][i] = 1。
- 如果有M[i][j] = 1,则有M[j][i] = 1。
2 并查集
2.1 思路
遍历每个学生,并从该学生的下一个序号开始处理,只要值为1代表两个学生为朋友,此时就进行并查集合并代表同属一个朋友圈。
最后返回并查集内不同root祖先个数,即得到朋友圈个数。
2.2 代码
class Solution {
public int findCircleNum(int[][] M) {
if(M == null || M.length == 0){
return 0;
}
// 并查集,大小为学生个数
int[] unionFindSet = new int[M.length];
// 初始化并查集值为元素序号本身
for(int i = 1; i < unionFindSet.length; i++){
unionFindSet[i] = i;
}
// 按行遍历每个学生
for(int i = 0; i < unionFindSet.length; i++){
// 从每个学生的下一个序号开始遍历处理朋友关系
for(int j = i + 1; j < unionFindSet.length; j++){
// 只要值为1,代表i学生和j号学生是朋友,则将他们的并查集合并
if(M[i][j] == 1){
join(unionFindSet, i, j);
}
}
}
// 用来统计朋友圈的HashSet
Set<Integer> filterSet = new HashSet<>();
// 遍历并查集,看不同的root祖先有几个就知道有几个不相连的并查集
// 即有几个朋友圈
for(int id : unionFindSet){
filterSet.add(search(unionFindSet, id));
}
return filterSet.size();
}
/**
* 将两个元素的祖先节点所在并查集合并
*/
private void join(int[] unionFindSet, int p, int q){
//
int left = search(unionFindSet,p);
int right = search(unionFindSet,q);
if (left == right){
// 同一并查集
return;
}
// 将p元素的root祖先节点的祖先设为q元素的root祖先节点
// 这样就合并为了一个并查集
unionFindSet[left] = right;
}
/**
* 寻找并查集root祖先
*/
private int search(int[] unionFindSet, int p){
int son = p;
// 从当前节点开始迭代查找祖先
// 直到某个元素祖先节点是自己,那就是找到了root祖先元素
while(p != unionFindSet[p]){
p = unionFindSet[p];
}
// 路径压缩算法,祖先的祖先全部设为p,高度变为2
while(son != p){
int tmp = unionFindSet[son];
unionFindSet[son] = p;
son = tmp;
}
// 返回root祖先元素
return p;
}
}
2.3 时间复杂度
2.4 空间复杂度
O(N)
3 图-DFS
3.1 思路
按行遍历学生,只要没访问过就说明属于一个新的独立朋友圈,count加1。此时,开始从下标为i+1的开始遍历其他学生j,只要M[i,j] == 1 说明是朋友,开始对学生j dfs。一趟dfs中遍历到的学生都属于这次i学生的朋友圈,只计数1次。
最后返回count即可。
3.2 代码
class Solution {
public int findCircleNum(int[][] M) {
if(M == null || M.length == 0){
return 0;
}
// 朋友圈个数
int count = 0;
// 记录是否已遍历
int[] visited = new int[M.length];
// 按行遍历每个学生
for(int i = 0; i < M.length; i++){
if(visited[i] == 1){
continue;
}
// 没人访问过该学生,说明该学生目前是个独立朋友圈
// 那么此时遍历到的关系为1的学生以及由此进行dfs访问到的学生同属i学生的一个朋友圈
count++;
// 标记该学生已访问过
visited[i] = 1;
// 从每个学生的下一个序号开始遍历处理朋友关系
for(int j = i + 1; j < M.length; j++){
// 只要值为1,代表i学生和j号学生是朋友,则将他们的朋友圈合并
if(visited[j] == 0 && M[i][j] == 1){
dfs(M, j, visited);
}
}
}
return count;
}
/**
* i作为朋友圈代号
* j为当前遍历学生序号
*/
private void dfs(int[][] M, int j, int[] visited){
visited[j] = 1;
// 这里注意要出0开始,因为有可能还没访问过的、序号更小的节点和当前节点是朋友
// 原因是采用了DFS,无顺序性!!
for(int k = 0; k < M.length; k++){
// 只要值为1,代表j学生和k号学生是朋友,则将他们的朋友圈合并
if(visited[k] == 0 && M[j][k] == 1){
dfs(M, k, visited);
}
}
}
}
3.3 时间复杂度
3.4 空间复杂度
O(N)
- 因为需要递归
4 BFS
4.1 思路
4.2 代码