题目难度: 中等
原题链接
今天继续来做并查集的问题, 这道题仍然比较基础, 而且也是个比较接近现实的问题了. 大家在我的公众号"每日精选算法题"中的聊天框中回复 并查集 就能看到该系列当前已经更新的文章了
大家有什么想法建议和反馈的话欢迎随时交流, 包括但不限于公众号聊天框/知乎私信评论等等~
题目描述
班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。
给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果 M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。
- N 在[1,200]的范围内。
- 对于所有学生,有 M[i][i] = 1。
- 如果有 M[i][j] = 1,则有 M[j][i] = 1。
题目样例
示例 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。
题目思考
- 朋友圈的个数可以等价于什么呢?
解决方案
思路
- 分析
- 要求朋友圈个数, 我们只需要找到是朋友的学生, 把他们归类在一起即可, 最终朋友圈就是集合的个数
- 推导
- 由于朋友关系是相互的, 具有对称性, 所以我们只需要遍历单方朋友关系即可, 即只需要遍历 i=>j(i<=j)的关系
- 然后对于朋友关系, 将两者 union 起来即可
- 最后再遍历所有人找他们的祖先 (注意此处仍然需要调用 find 而不是直接拿 pre 字典中存的结果, 具体原因参见上篇文章
[Leetcode 每日精选](本周主题-并查集) 990. 等式方程的可满足性
), 把祖先放到集合里, 最后集合长度即为所求.
- 实现
- 下面的代码中对每个步骤都有注释, 方便大家理解
复杂度
- 时间复杂度 O(N^2logN): 需要两重循环, 然后每次带有路径压缩的并查集的操作复杂度为 O(logN)
- 空间复杂度 O(N): pre 字典中存 N 个元素
代码
Python 3
class Solution:
def findCircleNum(self, M: List[List[int]]) -> int:
# 经典并查集模板
# 祖先字典
pre = {}
def find(x):
# 找x的祖先
if x not in pre:
# 祖先设为本身
pre[x] = x
elif pre[x] != x:
# 路径压缩, 更新当前祖先到更上面的祖先
pre[x] = find(pre[x])
return pre[x]
def union(x, y):
# 合并x和y到同一集合, 即把x的祖先的祖先指向y的祖先
pre[find(x)] = find(y)
n = len(M)
for i in range(n):
for j in range(i, n):
# 由于对称性, M[j][i]一定也是1, 所以此处只需要合并i和j即可, j不需要从头开始
if M[i][j] == 1:
union(i, j)
cntset = set()
for i in range(n):
# 注意这里使用find得到每个节点的最终祖先
cntset.add(find(i))
return len(cntset)
C++
class Solution {
public:
int findCircleNum(vector<vector<int>>& M) {
unordered_map<int, int> pre;
function<int(int)> find = [&](int x) {
if (pre.find(x) == pre.end()) {
pre[x] = x;
} else if (pre[x] != x) {
pre[x] = find(pre[x]);
}
return pre[x];
};
auto Union = [&](int x, int y) {
pre[find(x)] = find(y);
};
for (int i = 0; i < M.size(); ++i) {
for (int j = i; j < M.size(); ++j) {
if (M[i][j]) {
Union(i, j);
}
}
}
unordered_set<int> cntset;
for (int i = 0; i < M.size(); ++i) {
cntset.insert(find(i));
}
return cntset.size();
}
};
大家可以在下面这些地方找到我~
我的知乎专栏
我的 CSDN
我的 Leetcode
我的牛客网博客
我的公众号: 每日精选算法题, 欢迎大家扫码关注~