从账户合并看并查集的应用

从账户合并看并查集的应用

一、序言

上一篇博文已经系统讨论了并查集的各类问题,详情请参考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;
    }
}

猜你喜欢

转载自blog.csdn.net/qq_21515253/article/details/99948532