简介
并查集主要用于解决一些元素分组的问题,他管理一系列不相交的集合,并支持两种操作:
合并:把两个不相交的集合合并为一个集合
查询:查询两个元素是否在同一个集合中
模板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class UnionFind {
// 记录每个元素在哪个集合,也可以用 map 代替数组
int [] parent ;
public UnionFind ( int n ) {
parent = new int [ n ];
// 初始时每个元素都是分开的,即所在的集合是自己
for ( int i = 0 ; i < n ; i ++) {
parent [ i ] = i ;
}
}
// 将 x 合并到 y 所在的集合中
public void union ( int x , int y ) {
// 将 x 的集合也关联到 y 的集合中
parent [ find ( x )] = find ( y );
}
// 查找 x 所在的集合
public int find ( int x ) {
if ( parent [ x ] != x ) {
parent [ x ] = find ( parent [ x ]);
}
return parent [ x ];
}
}
例子
LC 721
给定一个列表 accounts
,每个元素 accounts[i]
是一个字符串列表,其中第一个元素 accounts[i][0]
是 名称 (name),其余元素是 emails
表示该账户的邮箱地址。
现在,我们想合并这些账户。如果两个账户都有一些共同的邮箱地址,则两个账户必定属于同一个人。请注意,即使两个账户具有相同的名称,它们也可能属于不同的人,因为人们可能具有相同的名称。一个人最初可以拥有任意数量的账户,但其所有账户都具有相同的名称。
合并账户后,按以下格式返回账户:每个账户的第一个元素是名称,其余元素是按字符 ASCII
顺序排列 的邮箱地址。账户本身可以以 任意顺序 返回。
输入:accounts = [["John", "johnsmith@mail.com", "john00@mail.com"], ["John", "johnnybravo@mail.com"], ["John", "johnsmith@mail.com", "john_newyork@mail.com"], ["Mary", "mary@mail.com"]]
输出:[["John", 'john00@mail.com', 'john_newyork@mail.com', 'johnsmith@mail.com'], ["John", "johnnybravo@mail.com"], ["Mary", "mary@mail.com"]]
解释:
第一个和第三个 John 是同一个人,因为他们有共同的邮箱地址 "johnsmith@mail.com"。
第二个 John 和 Mary 是不同的人,因为他们的邮箱地址没有被其他帐户使用。
可以以任何顺序返回这些列表,例如答案 [['Mary','mary@mail.com'],['John','johnnybravo@mail.com'],
['John','john00@mail.com','john_newyork@mail.com','johnsmith@mail.com']] 也是正确的。
思路:
将每个邮箱标号(邮箱->标号),并通过 map 来管理其对应关系
创建并查集,并查集管理的是邮箱的标号
遍历列表,将每个账户的邮箱通过并查集合并到一块
查找并查集,将每个集合的标号都放到 List 里。
新建一个 map,存放标号和账户名称(标号 -> 账户名称)
新建一个 map,存放标号和邮箱(标号 -> 邮箱)
从 List 中的第一个标号找到名称,并且找到邮箱,存放到结果 map 中
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
class Solution {
public List < List < String >> accountsMerge ( List < List < String >> accounts ) {
Map < String , Integer > emailToIndex = new HashMap <>();
Map < Integer , String > indexToEmail = new HashMap <>();
Map < Integer , String > indexToName = new HashMap <>();
int accountIndex = 0 ;
for ( List < String > account : accounts ) {
int size = account . size ();
for ( int i = 1 ; i < size ; i ++) {
indexToName . put ( accountIndex , account . get ( 0 )); // 每个邮箱对应一个名字
indexToEmail . put ( accountIndex , account . get ( i ));
if (! emailToIndex . containsKey ( account . get ( i ))) // 相同邮箱只有一个下标
emailToIndex . put ( account . get ( i ), accountIndex ++);
}
}
UnionFind uf = new UnionFind ( accountIndex );
// 将邮箱添加到并查集中
for ( List < String > account : accounts ) {
int size = account . size ();
int firstIndex = emailToIndex . get ( account . get ( 1 ));
for ( int i = 2 ; i < size ; i ++) {
int nextIndex = emailToIndex . get ( account . get ( i ));
uf . union ( firstIndex , nextIndex );
}
}
// 根据并查集查找每个集合,并放入 map 中
HashMap < Integer , List < Integer >> map = new HashMap <>();
for ( int i = 0 ; i < accountIndex ; i ++) {
int parent = uf . find ( i );
System . out . println ( parent );
List < Integer > list = map . getOrDefault ( parent , new ArrayList < Integer >());
list . add ( i );
map . put ( parent , list );
}
// 根据 map 转换为 list,并把邮箱排序
List < List < String >> res = new ArrayList <>();
for ( Integer key : map . keySet ()) {
String name = indexToName . get ( key );
List < String > list = new ArrayList <>();
for ( int i : map . get ( key )) {
list . add ( indexToEmail . get ( i ));
}
Collections . sort ( list );
list . add ( 0 , name );
res . add ( list );
}
return res ;
}
}
class UnionFind {
int [] parent ;
public UnionFind ( int n ) {
parent = new int [ n ];
for ( int i = 0 ; i < n ; i ++) {
parent [ i ] = i ;
}
}
public void union ( int index1 , int index2 ) {
parent [ find ( index2 )] = find ( index1 );
}
public int find ( int index ) {
if ( parent [ index ] != index ) {
parent [ index ] = find ( parent [ index ]);
}
return parent [ index ];
}
}