LeetCode - 547. Friend Circles
There are N students. Some of them are friends, while some are not. Their friendship is transitive in nature.
For example, if A is a friend of B, and B is a friend of C, then A is an friend of C.
And we defined a friend circle is a group of students who are friends.
Given a N*N matrix M representing the friend relationship between students in the class.
If M[i][j] = 1, then the ith and jth students are friends with each other, otherwise not.
And you have to output the total number of friend circles among all the students.
Note: N is in range [1,200]. M[i][i] = 1 for all students. If M[i][j] = 1, then M[j][i] = 1.
就是说用一个类似邻接矩阵的方阵来表示谁和谁是朋友,核心思想就是朋友的朋友就是朋友。这很符合并查集的原则,将每一个圈子的人用一个各自 leader 来表示,然后判断两个人是不是一个圈子的,只需要判断他们 leader 是否是同一个人即可。比如把所有腾讯的员工 union 到一起,就是让他们的最大 leader 都变成 MHT,当然他们每个人可能有自己的上司,上司又有自己的上司,但是通过这个找上司的过程找下去,一定最后会找到 MHT,这样他们就是一个圈子的了,同理把阿里的员工 union 到一起,他们的最高 leader 都是 MY,那么判断两个人是不是一个圈子的,只需要判断他们的最高 leader 是不是一个人就行了。这就引发了下边几个问题:
- 怎么表示记录自己的上司?
用一个数组 pre,pre[i] 记录 pre 的上司; - 怎么表示 leader?
pre[i] = i 的就是 leader,也就是最高领导; - 怎么初始化 pre 数组?
初始化所有 pre[i] = i,也就是最开始所有人是自己的最高领导; - 查找 leader 的过程是否可以加速?
可以,查找的出一个人的 leader 后,把这个人以及他所有上司,都变成 leader 的直接下属,也就是只有根节点和一层叶子节点的树; - 怎么知道最后有多少个圈子?
用一个 unordered_set 记录一共有多少个 leader。
用代码讲,下边这个 unionFind 就是用来查找一个人的 leader 并进行路径压缩的。
// 返回n的leader, 并进行路径压缩
int unionFind(int n) {
int leader = n;
while(leader != pre[leader]) // 找到n的掌门: leader
leader = pre[leader];
// 路径压缩, 路径上所有人都变成 leader 的直接下属
while(pre[n] != leader) {
const int tmp = pre[n];
pre[n] = leader;
n = tmp;
}
return leader;
}
unionUnion 用来将 a 和 b 合并成一个圈子,当 a 和 b 的 leader 不同时,随便让其中一个 leader 变成另一个 leader 的下属就可以了(这时候树可能就不是只有两层了),就好像公司被别人收购了一样= =。
// 合并a和b的圈子
void unionUnion(int a, int b) {
const int x = unionFind(a), y = unionFind(b);
if(x != y)
pre[x] = y; // 让一个成为另一个的下属
}
这道 Friend Circles 的题就很容易写了:
vector<int> pre;
// 返回n的leader, 并进行路径压缩
int unionFind(int n) {
int leader = n;
while(leader != pre[leader]) // 找到n的掌门: leader
leader = pre[leader];
while(pre[n] != leader) {
const int tmp = pre[n];
pre[n] = leader;
n = tmp;
}
return leader;
}
// 合并a和b的圈子
void unionUnion(int a, int b) {
const int x = unionFind(a), y = unionFind(b);
if(x != y)
pre[x] = y; // 让一个成为另一个的下属
}
int findCircleNum(vector<vector<int>>& M) {
pre = vector<int>(201);
// 初始化每个人为自己的 leader
for(int i = 0; i < pre.size(); ++i)
pre[i] = i;
for (int i = 0; i < M.size(); ++i)
for (int j = i + 1; j < M.size(); ++j)
if (M[i][j])
unionUnion(i, j);
int cnt = 0;
unordered_set<int> st;
for(int i = 0; i < M.size(); ++i){
const int leader = unionFind(i);
if(st.find(leader) == st.end())
st.insert(leader);
}
return st.size();
}
并查集原来一行就可以完成查找和路径压缩:
int find(int x) {
return x == f[x] ? x : f[x] = find(f[x]);
}