721. 账户合并(Python)

题目

难度:★★★★☆
类型:图
方法:并查集

力扣链接请移步本题传送门
更多力扣中等题的解决方案请移步力扣中等题目录

给定一个列表 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']]仍然会被接受。

注意:

accounts的长度将在[1,1000]的范围内。
accounts[i]的长度将在[1,10]的范围内。
accounts[i][j]的长度将在[1,30]的范围内。

解答

这道题是一个典型的图的问题,我们使用并查集来做。这里比较推荐的参考资料是某大佬的并查集总结

定义邮件的并查集UF(Union Find的缩写),包含寻找根节点(find)和合并(union)两个方法,UF包含一个字典,或者说哈希表,记录了父节点与子节点的联系关系。实际上,我们把并查集的树形结构中的每一条边都用哈希对应的方式来表达,这样,一棵树就在字典里了。

我们在构建并查集时,为了避免后续使用时查找失败,就先存放好所有邮件对应的父节点,即自己。

为了获得邮件与用户的归属关系,我们建立一个哈希表,email_to_use。

此外,对于每一个邮件列表,我们都让它们通过合并,使得在并查集的同一个连通域内。因此,造成的结果是,只要两个邮件列表中有一样的邮件地址,那么这两个列表中所有的邮件地址,都在同一个联通域内,或者说,它们有一样的根节点,这个根节点可以通过find方法找到。

只要找到了连通域,从中选择任意一个邮件,就可以通过邮件-用户对应表寻找到用户,也就可以完成邮件的合并了。

import collections


class UF:
    def __init__(self, accounts):
        self.parent = dict()
        for account in accounts:
            for item in account[1:]:
                self.parent[item] = item

    def find(self, x):
        while x != self.parent[x]:
            x = self.parent[x]
        return x

    def union(self, p, q):
        self.parent[self.find(p)] = self.find(q)


class Solution:
    def accountsMerge(self, accounts):
        uf = UF(accounts)
        email_to_name = {}

        # 1. 构建邮件地址-姓名映射表,2. 建立邮件地址的并查集
        for account in accounts:
            user, addresses = account[0], account[1:]
            for i, address in enumerate(addresses):
                email_to_name[address] = user
                if i < len(addresses) - 1:
                    uf.union(address, addresses[i+1])

        # 归并并查集中的联通区
        root_to_emails = collections.defaultdict(list)
        for email in email_to_name.keys():
            root_to_emails[uf.find(email)].append(email)
        res = [[email_to_name[value[0]]] + sorted(value) for value in root_to_emails.values()]
        return res

如有疑问或建议,欢迎评论区留言~

有关更多力扣中等题的python解决方案,请移步力扣中等题解析

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容