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

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

一、序言

上一篇博文已经系统讨论了并查集的各类问题,详情请参考https://blog.csdn.net/qq_21515253/article/details/99703065

本次基于leetcode上的一个账户合并问题,我们更进一步,此问题把并查集运用的淋漓尽致,想与大家探讨下这个题目。

二、题目

给定一个列表 accounts,每个元素 accounts[i] 是一个字符串列表,其中第一个元素 accounts[i][0] 是 名称 (name),其余元素是 emails 表示该帐户的邮箱地址。

现在,我们想合并这些帐户。如果两个帐户都有一些共同的邮件地址,则两个帐户必定属于同一个人。请注意,即使两个帐户具有相同的名称,它们也可能属于不同的人,因为人们可能具有相同的名称。一个人最初可以拥有任意数量的帐户,但其所有帐户都具有相同的名称。

合并帐户后,按以下格式返回帐户:每个帐户的第一个元素是名称,其余元素是按顺序排列的邮箱地址。accounts 本身可以以任意顺序返回。

例子 1:
Input: 
accounts = [["John", "johnsmith@mail.com", "john00@mail.com"], ["John", "johnnybravo@mail.com"], ["John", "johnsmith@mail.com", "john_newyork@mail.com"], ["Mary", "mary@mail.com"]]
Output: [["John", 'john00@mail.com', 'john_newyork@mail.com', 'johnsmith@mail.com'],  ["John", "johnnybravo@mail.com"], ["Mary", "mary@mail.com"]]
Explanation: 
  第一个和第三个 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']]仍然会被接受。

三、思路(建图)

本题最直接的思路无非就是遍历所有邮箱,当遇到前面出现的进行合并。

而合并的操作很容易让我们和并查集的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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值