从账户合并看并查集的应用
一、序言
上一篇博文已经系统讨论了并查集的各类问题,详情请参考https://blog.csdn.net/qq_21515253/article/details/99703065
本次基于leetcode上的一个账户合并问题,我们更进一步,此问题把并查集运用的淋漓尽致,想与大家探讨下这个题目。
二、题目
给定一个列表 accounts,每个元素 accounts[i] 是一个字符串列表,其中第一个元素 accounts[i][0] 是 名称 (name),其余元素是 emails 表示该帐户的邮箱地址。
现在,我们想合并这些帐户。如果两个帐户都有一些共同的邮件地址,则两个帐户必定属于同一个人。请注意,即使两个帐户具有相同的名称,它们也可能属于不同的人,因为人们可能具有相同的名称。一个人最初可以拥有任意数量的帐户,但其所有帐户都具有相同的名称。
合并帐户后,按以下格式返回帐户:每个帐户的第一个元素是名称,其余元素是按顺序排列的邮箱地址。accounts 本身可以以任意顺序返回。
例子 1:
Input:
accounts = [["John", "[email protected]", "[email protected]"], ["John", "[email protected]"], ["John", "[email protected]", "[email protected]"], ["Mary", "[email protected]"]]
Output: [["John", '[email protected]', '[email protected]', '[email protected]'], ["John", "[email protected]"], ["Mary", "[email protected]"]]
Explanation:
第一个和第三个 John 是同一个人,因为他们有共同的电子邮件 "[email protected]"。
第二个 John 和 Mary 是不同的人,因为他们的电子邮件地址没有被其他帐户使用。
我们可以以任何顺序返回这些列表,例如答案[['Mary','[email protected]'],['John','[email protected]'],
['John','[email protected]','[email protected]','[email protected]']]仍然会被接受。
三、思路(建图)
本题最直接的思路无非就是遍历所有邮箱,当遇到前面出现的进行合并。
而合并的操作很容易让我们和并查集的union操作联系在一起。
而此时,我们需要思考的是,如何建图的问题。
那建图的最终目的是什么??(或者说并查集的目的??)
保证同一个人的email处于同一个连通块中,即同一棵树上!!!!!
1、选点
我们把每个邮箱地址当成点,为什么这么选取呢?
(因为emai具有唯一性,而name没有)
2、取边
对于accounts中的一个List
[“name”,“emal1”,“emai2”,“email3”]
我们只需让eami1与eaml2相连,email2与eamls3相连,则三个email就两两相连,处于同一个连通块中
3、union-find
我们只需对边做union操作,同时构建一个树林,把所有的emai连在同一棵树上即可。
四、代码解析
1、首先,我们需要一个构建树林功能的并查集类
class UnionFind {
HashMap<String, String> parent = new HashMap<>();
HashMap<String, Integer> size = new HashMap<>();
HashSet<String> top = new HashSet<>();
//树林!!!!!!!!!!!!!!!!!!!!!!!!!!!!
HashMap<String, ArrayList<String>> topTree = new HashMap<>();
public UnionFind() {
}
public void union(String node1, String node2) {
if (!parent.containsKey(node1)) {
parent.put(node1, node1);
size.put(node1, 1);
ArrayList<String> newSet = new ArrayList<>();
newSet.add(node1);
topTree.put(node1, newSet);
}
if (!parent.containsKey(node2)) {
parent.put(node2, node2);
size.put(node2, 1);
ArrayList<String> newSet = new ArrayList<>();
newSet.add(node2);
topTree.put(node2, newSet);
}
String root1 = find(node1);
String root2 = find(node2);
if (root1.equals(root2)) {
return;
}
if (size.get(root1) < size.get(root2)) {
size.put(root2, size.get(root1) + size.get(root2));
parent.put(root1, root2);
top.remove(root1);
ArrayList<String> addSet = topTree.get(root1);
ArrayList<String> newSet = topTree.get(root2);
newSet.addAll(addSet);
topTree.put(root2, newSet);
topTree.remove(root1);
} else {
size.put(root1, size.get(root1) + size.get(root2));
parent.put(root2, root1);
top.remove(root2);
ArrayList<String> addSet = topTree.get(root2);
ArrayList<String> newSet = topTree.get(root1);
newSet.addAll(addSet);
topTree.put(root1, newSet);
topTree.remove(root2);
}
}
public String find(String node) {
if (!parent.containsKey(node)) {
parent.put(node, node);
}
while (!(node.equals (parent.get(node)))) {
node = parent.get(node);
}
return node;
}
public boolean connected(String node1, String node2) {
return find(node1).equals(find(node2));
}
public int getTop() {
return top.size();
}
public HashMap<String, ArrayList<String>> getTopTree() {
return topTree;
}
}
2、只需把String类型的email当成点,遍历构造边即可
注意:保存邮箱地址对应的名字的hash,便于输出
class Solution {
public List<List<String>> accountsMerge(List<List<String>> accounts) {
//email---name hash
HashMap<String, String> emailToName = new HashMap<>();
//union-find
UnionFind uf = new UnionFind();
//边构造+union
for (int i = 0; i < accounts.size(); i++) {
for (int j = 1; j < accounts.get(i).size() - 1; j++) {
emailToName.put(accounts.get(i).get(j), accounts.get(i).get(0));
uf.union(accounts.get(i).get(j), accounts.get(i).get(j + 1));
}
if (accounts.get(i).size() == 2) {
uf.union(accounts.get(i).get(1), accounts.get(i).get(1));
}
emailToName.put(accounts.get(i).get(accounts.get(i).size() - 1), accounts.get(i).get(0));
}
//输出email树林
HashMap<String, ArrayList<String>> topTree = uf.getTopTree();
//程序输出,注意排序
List<List<String>> out = new ArrayList<>();
for (String emailKey : topTree.keySet()) {
ArrayList<String> now = new ArrayList();
now.addAll(topTree.get(emailKey));
//sort后输出
Collections.sort(now);
now.add(0, emailToName.get(emailKey));
out.add(now);
}
//System.out.println(out);
return out;
}
}