LeetCode - 721. 账户合并

描述

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

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

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

 

示例 1:

输入:
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']] 也是正确的。
 

提示:

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/accounts-merge/
 

求解

    class UnionFind {
    public:
        UnionFind(int n) : count(n) {
            parent.reserve(count + 1);
            for (int i = 0; i <= count; ++i) {
                parent[i] = i;
            }
            rank.resize(count + 1, 1);  // 初始每个的层级均为1
        }

        bool isConnected(int p, int q) {
            return find(p) == find(q);
        }

        void unionElements(int p, int q) {
            int proot = find(p);
            int qroot = find(q);
            if (proot == qroot) {
                return;
            }

            if (rank[proot] < rank[qroot]) {
                parent[proot] = qroot;
            } else if (rank[proot] > rank[qroot]) {
                parent[qroot] = proot;
            } else {
                // rank[proot] == rank[qroot]
                parent[proot] = qroot;
                ++rank[qroot];  // proot ”挂载“到qroot下面,本来两个层级一致,现在需要增加1
            }
        }

        int find(int p) {
            while (p != parent[p]) {
                parent[p] = parent[parent[p]]; // 路径压缩优化,请细品
                p = parent[p];
            }
            return p;
        }

    private:
        std::vector<int> parent;
        int count;
        std::vector<int> rank;
    };

    class Solution {
    public:
        // 方法一,超时不通过,有太多字符串拷贝
        vector<vector<string>> accountsMerge_1e(vector<vector<string>> &accounts) {
            std::unordered_multimap<string, std::set<string>> record; // <账户, 邮箱集合>
            for (auto &vec : accounts) {
                auto iterPair = record.equal_range(vec[0]);
                // 记录中不存在同账户名的信息
                if (iterPair.first == iterPair.second) {
                    record.emplace(vec[0], std::set<string>(vec.begin() + 1, vec.end()));
                    continue;
                }
                // 记录中存在同账户名的信息,需要判断是否和某个同名账户的邮件是否相同,如果相同合并到该账户下(什么也不做)
                // 如果和同名账户都没有相同的邮箱,新创建一个
                const int emailNum = vec.size();
                vector<decltype(iterPair.first)> iterC;
                for (auto iter = iterPair.first; iter != iterPair.second; ++iter) {
                    for (int i = 1; i < emailNum; ++i) {
                        if (iter->second.count(vec[i]) != 0) {
                            iterC.push_back(iter);
                            break;
                        }
                    }
                }

                // 所有同名账户中均搜索不到相等的邮箱,确定是一个新的账户
                if (iterC.empty()) {
                    record.emplace(vec[0], std::set<string>(vec.begin() + 1, vec.end()));
                    continue;
                }

                // iterC.size() > 0
                // 场景一,在仅有一个同名账户中找到相同邮箱,则将其他邮箱合并入该账户,iterC.size() == 1
                // 场景二,在多个个同名账户中找到相同邮箱,则需要将这些邮箱均合并,且将新的邮件合并入该账户,iterC.size() > 1
                std::pair<string, std::set<string>> newInfo{iterC[0]->first, iterC[0]->second};
                for (int i = 1; i < iterC.size(); ++i) {
                    newInfo.second.insert(iterC[i]->second.begin(), iterC[i]->second.end());
                }
                newInfo.second.insert(vec.begin() + 1, vec.end());
                for (auto it : iterC) {
                    record.erase(it);
                }
                record.insert(newInfo);
            }

            // 结果处理
            vector<vector<string>> res;
            for (auto &[name, emailsSet] : record) {
                vector<string> info{name}; // 账户名
                info.insert(info.end(), emailsSet.begin(), emailsSet.end()); // 邮箱集合
                res.emplace_back(std::move(info));
            }
            return res;
        }

        // 方法一优化字符串拷贝,采用move,低效率通过
        // 思想复杂,但是代码写的过于复杂,特别是加上了移动语义,对不住了,哈哈...估计只有我自己能看懂
        vector<vector<string>> accountsMerge_2e(vector<vector<string>> &accounts) {
            std::unordered_multimap<string, std::set<string>> record; // <账户, 邮箱集合>
            for (auto &vec : accounts) {
                auto iterPair = record.equal_range(vec[0]);
                // 记录中不存在同账户名的信息
                if (iterPair.first == iterPair.second) {
                    record.emplace(std::move(vec[0]),
                                   std::move(std::set<string>(make_move_iterator(vec.begin() + 1),
                                                              make_move_iterator(vec.end())))); // 资源移动而不是拷贝
                    continue;
                }
                // 记录中存在同账户名的信息,需要判断是否和某一个(或者多个)同名账户的邮件是否相同,如果相同合并到该账户下(什么也不做)
                // 如果和同名账户都没有相同的邮箱,新创建一个
                const int emailNum = vec.size();
                vector<decltype(iterPair.first)> iterC;
                for (auto iter = iterPair.first; iter != iterPair.second; ++iter) {
                    for (int i = 1; i < emailNum; ++i) {
                        if (iter->second.count(vec[i]) != 0) {
                            iterC.emplace_back(iter);
                            break;
                        }
                    }
                }

                // 所有同名账户中均搜索不到相等的邮箱,确定是一个新的账户
                if (iterC.empty()) {
                    record.emplace(std::move(vec[0]),
                                   std::move(std::set<string>(make_move_iterator(vec.begin() + 1),
                                                              make_move_iterator(vec.end())))); // 资源移动而不是拷贝
                    continue;
                }

                // iterC.size() > 0
                // 场景一,在仅有一个同名账户中找到相同邮箱,则将其他邮箱合并入该账户,iterC.size() == 1
                // 场景二,在多个个同名账户中找到相同邮箱,则需要将这些邮箱均合并,且将新的邮件合并入该账户,iterC.size() > 1
                std::pair<string, std::set<string>> newInfo{std::move(iterC[0]->first),
                                                            std::move(iterC[0]->second)}; // 资源移动而不是拷贝
                for (int i = 1; i < iterC.size(); ++i) {
                    newInfo.second.insert(make_move_iterator(iterC[i]->second.begin()),
                                          make_move_iterator(iterC[i]->second.end()));  // 没有移动
                }
                newInfo.second.insert(make_move_iterator(vec.begin() + 1), make_move_iterator(vec.end())); // 资源移动而不是拷贝
                for (auto it : iterC) {
                    record.erase(it);
                }
                record.insert(std::move(newInfo)); // 资源移动而不是拷贝
            }

            // 结果处理
            vector<vector<string>> res;
            for (auto &[name, emailsSet] : record) {
                vector<string> info{std::move(name)}; // 账户名,资源移动而不是拷贝
                // 邮箱集合,?似乎没有进行资源移动,而是拷贝
                info.insert(info.end(), make_move_iterator(emailsSet.begin()), make_move_iterator(emailsSet.end()));
                res.emplace_back(std::move(info)); // 资源移动而不是拷贝
            }
            return res;
        }

        // 方法三,并查集
        vector<vector<string>> accountsMerge_3e(vector<vector<string>> &accounts) {
            const int n = accounts.size();
            UnionFind uf(n);
            // <邮箱,用户ID>集合
            std::unordered_map<string, int> mailUserRec;
            for (int i = 0; i < n; ++i) {
                int mailNum = accounts[i].size();
                for (int j = 1; j < mailNum; ++j) {
                    string &mail = accounts[i][j];
                    // 邮箱未出现过,加入映射集合
                    if (mailUserRec.count(mail) == 0) {
                        mailUserRec[mail] = i;
                        continue;
                    }
                    // 邮箱出现过,将两个用户关联
                    uf.unionElements(i, mailUserRec[mail]);
                }
            }

            // <用户,邮箱集合>
            // 下面过程中,根据并查集的关系,有关联的用户(即相同用户)的邮箱将被合在一起
            std::unordered_map<int, vector<string>> rec;
            for (auto &[mail, userId] : mailUserRec) {
                rec[uf.find(userId)].emplace_back(mail);
            }

            // 根据用户ID映射真实用户名,排序同用户邮箱
            vector<vector<string>> res;
            for (auto &[userId, mail]  : rec) {
                vector<string> oneUserInfo(1, accounts[userId][0]);
                std::sort(mail.begin(), mail.end());
                oneUserInfo.insert(oneUserInfo.end(), mail.begin(), mail.end());
                res.emplace_back(oneUserInfo);
            }
            return res;
        }

        // 方法三优化字符串拷贝,采用move
        vector<vector<string>> accountsMerge(vector<vector<string>> &accounts) {
            const int n = accounts.size();
            UnionFind uf(n);
            // <邮箱,用户ID>集合
            std::unordered_map<string, int> mailUserRec;
            for (int i = 0; i < n; ++i) {
                int mailNum = accounts[i].size();
                for (int j = 1; j < mailNum; ++j) {
                    string &mail = accounts[i][j];
                    // 邮箱未出现过,加入映射集合
                    if (mailUserRec.count(mail) == 0) {
                        mailUserRec[mail] = i;
                        continue;
                    }
                    // 邮箱出现过,将两个用户关联
                    uf.unionElements(i, mailUserRec[mail]);
                }
            }

            // <用户,邮箱集合>
            // 下面过程中,根据并查集的关系,有关联的用户(即相同用户)的邮箱将被合在一起
            std::unordered_map<int, vector<string>> rec;
            for (auto &[mail, userId] : mailUserRec) {
                rec[uf.find(userId)].emplace_back(std::move(mail));  // 移动字符串
            }

            // 根据用户ID映射真实用户名,排序同用户邮箱
            vector<vector<string>> res;
            for (auto &[userId, mail]  : rec) {
                vector<string> oneUserInfo(1, accounts[userId][0]);
                std::sort(mail.begin(), mail.end());
                // make_move_iterator
                oneUserInfo.insert(oneUserInfo.end(), make_move_iterator(mail.begin()), make_move_iterator(mail.end()));
                res.emplace_back(std::move(oneUserInfo)); // 移动字符串
            }
            return res;
        }
    };

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值