LeetCode-721. Fusión de cuentas

descripción

Dada una lista de cuentas, cada elemento cuentas [i] es una lista de cadenas, donde el primer elemento cuentas [i] [0] es el nombre (nombre), y los elementos restantes son correos electrónicos que representan la dirección de correo electrónico de la cuenta.

Ahora, queremos fusionar estas cuentas. Si dos cuentas tienen algunas direcciones de correo electrónico en común, las dos cuentas deben pertenecer a la misma persona. Tenga en cuenta que incluso si dos cuentas tienen el mismo nombre, pueden pertenecer a diferentes personas, porque las personas pueden tener el mismo nombre. Una persona puede tener inicialmente cualquier número de cuentas, pero todas sus cuentas tienen el mismo nombre.

Después de fusionar las cuentas, las cuentas se devuelven en el siguiente formato: el primer elemento de cada cuenta es el nombre y los elementos restantes son las direcciones de correo electrónico en orden. La cuenta en sí se puede devolver en cualquier orden.

 

Ejemplo 1:

Ingresa:
cuentas = [["John", "[email protected]", "[email protected]"], ["John", "[email protected]"], ["John", "johnsmith @ mail .com "," [email protected] "], [" Mary "," [email protected] "]]
Salida:
[[" John ", '[email protected]', '[email protected]' , 'johnsmith @ mail.com'], ["John", "[email protected]"], ["Mary", "[email protected]"]]
Explicación: El
primer y tercer John son las mismas personas, porque tienen una dirección de correo electrónico común "[email protected]". 
El segundo John y Mary son personas diferentes porque sus direcciones de correo electrónico no son utilizadas por otras cuentas.
Estas listas se pueden devolver en cualquier orden, como las respuestas [['Mary', '[email protected]'], ['John', '[email protected]'],
['John', 'john00 @ mail .com '
 

rápido:

La longitud de las cuentas estará en el rango de [1, 1000].
La longitud de las cuentas [i] estará en el rango de [1, 10].
La longitud de las cuentas [i] [j] estará en el rango de [1, 30].

Fuente:
Enlace de LeetCode : https://leetcode-cn.com/problems/accounts-merge/
 

Resolver

    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;
        }
    };

 

Supongo que te gusta

Origin blog.csdn.net/u010323563/article/details/112791716
Recomendado
Clasificación