被字节跳动的题目难住了,感到了自己认识的差距。希望可以通过博客记录自己学到的算法或者方式吧,鼓励自己。
Bytedance Efficiency Engineering团队在8月20日搬入了学清嘉创大厦,为庆祝团队的乔迁之喜,字节君决定邀请整个EE团队,举办一个大型团建游戏-字节跳动大闯关。可是遇到了一个问题:
EE团队共有n个人,大家都比较害羞,不善于与陌生人交流。这n个人每个人都向字节君提供了自己认识的人的名字,不包括自己。如果A的名单里有B,或B的名单里有A,则代表A与B相互认识。同时,如果A认识B,B认识C,则代表A与C也会很快的认识,毕竟通过B的介绍,两个人就可以很快相互认识的了。
为了大闯关游戏可以更好地团队写作、气氛更活跃,并使得团队中的人可以尽快的相互了解、认识和交流,字节君决定根据这个名单将团队分为m组,每组人数可以不同,但组内的任何一个人都与组内的其他所有人直接或间接的认识和交流。如何确定一个方案,使得团队可以分为m组,并且这个m尽可能地小呢?
输入描述:
第一行一个整数n,表示有n个人,从1开始编号
接下来n行,分别表示第i个人认识的人的编号k(1<=k<=n),每个人的名单以0代表结束
输出描述:
一个整数m,代表可以闻的最小的组的个数
很明显,这是一个典型的并查集的问题。没见过这类题的我表示在笔试的时候一脸懵逼。
笔试结束后,我的大佬告诉我,这题可以用并查集做。于是乎,并查集来了。
思想大致如此:
1、初始化集合
把每个点所在集合初始化为其自身,通常来说这个步骤在每次使用该数据结构时只需要执行一次,无论何种实现方式,时间复杂度O(n)
vector<int> rela(n+1,0);//构建集合
//初始化集合,使每个集合指向自己
void build( const int s ){
for( int i=1; i<=s; ++i ){
rela[i] = i;
}
return;
}
2、查找元素所在集合,即根节点
其中,还包含路径压缩方法。若rela[x] != x时,再向上继续寻找,并将rela更新,即rela[x] = findRela(rela[x])
int findRela( const int x ){
return rela[x]==x ? x : rela[x]=findRela(rela[x]);
}
3、合并元素
在合并之前,需要判断两个元素是否属于同一集合,可用步骤2进行查找
//方法1、判断是否同一集合,并合并
void merge_bingchaji( int x, int y ){
if( findRela(x) != findRela(y) ) //判断是否属于同一集合
rela[rela[x]] = rela[y];
}
//方法2、将判断与合并分开
//在需要查询是否同一集合时,必须将check独立出来
bool check( int x, int y ){
return findRela(x) == findRela(y);
}
void merge_bingchaji( int x, int y ){
if( !check(x,y) )
rela[rela[x]] = rela[y];
}
以上就是并查集的基本思想。接下来根据题目进行分析
全部代码如下
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int n;
vector<int> rela(n+1,0);//构建集合
//初始化集合,使每个集合指向自己
void build( const int s ){
for( int i=1; i<=s; ++i ){
rela[i] = i;
}
return;
}
//寻找祖父,并缩短路径
int findRela( const int x ){
return rela[x]==x ? x : rela[x]=findRela(rela[x]);
}
//判断是否属于同一集合
bool check( const int x, const int y ){
return findRela(x)==findRela(y);
}
//合并两个集合
void hebin( const int x, const int y ){
rela[rela[x]] = rela[y];
}
int main()
{
cin >> n; //总共人数
build(n); //初始化集合,共有n个集合
cout << "初始集合结果为:";
for( int j=1; j<=n; ++j ){
cout << rela[j] << " ";
}
cout << endl;
for( int i=1; i<=n; ++i ){
int s;
while( (cin >> s) ){
if( s == 0 )
break;
if( !check(i, s) ) //若他们的祖父不相等,则合并两个集合
hebin(i, s);
cout << i << "认识" << s << " 集合结果为:";
for( int j=1; j<=n; ++j ){
cout << rela[j] << " ";
}
cout << endl;
}
}
vector<int> temp; //设置集合个数
for( int i=1; i<=n; ++i ){ //若在集合中未找到这个祖父,则将祖父加入其中
if( find(temp.begin(), temp.end(), findRela(i)) == temp.end() )
temp.push_back(findRela(i));
}
cout << "可分为:" << temp.size() << "组"<< endl;
return 0;
}
输出参考样例
10
0
5 3 0
8 4 0
9 0
9 0
3 0
0
7 9 0
0
9 7 0
10
初始集合结果为:1 2 3 4 5 6 7 8 9 10
0
5 3 0
2认识5 集合结果为:1 5 3 4 5 6 7 8 9 10
2认识3 集合结果为:1 5 3 4 3 6 7 8 9 10
8 4 0
3认识8 集合结果为:1 5 8 4 3 6 7 8 9 10
3认识4 集合结果为:1 5 8 4 3 6 7 4 9 10
9 0
4认识9 集合结果为:1 5 8 9 3 6 7 4 9 10
9 0
5认识9 集合结果为:1 5 9 9 9 6 7 9 9 10
3 0
6认识3 集合结果为:1 5 9 9 9 9 7 9 9 10
0
7 9 0
8认识7 集合结果为:1 5 9 9 9 9 7 9 7 10
8认识9 集合结果为:1 5 9 9 9 9 7 7 7 10
0
9 7 0
10认识9 集合结果为:1 5 9 9 9 9 7 7 7 7
10认识7 集合结果为:1 5 9 9 9 9 7 7 7 7
可分为:2组
最后结果为 1 5 9 9 9 9 7 7 7 7
其中原因为某些路径并没有被压缩越减,故导致它并不是直接指向根节点 。但若再经过几次查找,最后的指向肯定会是2组别的。从最后的输出也能看出来是两组。完美,并查集打卡!