并查集

简介

并查集主要用于解决一些元素分组的问题,他管理一系列不相交的集合,并支持两种操作:

  • 合并:把两个不相交的集合合并为一个集合
  • 查询:查询两个元素是否在同一个集合中

模板

 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];
    }
}
updatedupdated2022-06-232022-06-23