描述
给定一个列表 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;
}
};