题目
给定一个列表 accounts,每个元素 accounts[i] 是一个字符串列表,其中第一个元素 accounts[i][0] 是 名称 (name),其余元素是 emails 表示该账户的邮箱地址。
现在,我们想合并这些账户。如果两个账户都有一些共同的邮箱地址,则两个账户必定属于同一个人。请注意,即使两个账户具有相同的名称,它们也可能属于不同的人,因为人们可能具有相同的名称。一个人最初可以拥有任意数量的账户,但其所有账户都具有相同的名称。
合并账户后,按以下格式返回账户:每个账户的第一个元素是名称,其余元素是按字符 ASCII 顺序排列的邮箱地址。账户本身可以以任意顺序返回。
示例 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]的范围内。
思路
还无疑问,今天还是并查集。本来以为可以做的很快,然而我还是太天真了。
解析
- 按照题目描述,毫无疑问的可以使用并查集来解决。首先每个连通图的基本就是一个用户,而所有的邮箱则是根据不同的用户来进行操作。而出现在不同容器中的用户的邮箱只要相同,则可以判断他们为一个用户。
- 并查集的框架好说,难点是怎么构造并查集。首先,这里有两个变量:用户,邮箱。按照经验,肯定是用邮箱来操作并查集结构。但是,我们怎么判断两个邮箱可以合并呢?按照常规步骤,先对每一个邮箱创建一个email_to_index的映射(将邮箱映射为index(整型变量))便于UF的操作,同时去除相同的邮箱!!紧接着就是合并的问题。
- 如果要将邮箱按照拥有者来进行构造图,就必须还要其相关的拥有者。其实这里可以直接按照所给的accounts来进行操作,因为每一组其实就是一个用户及其邮箱的对应,因此,可以直接通过其进行循环,将每一组依次合并。但是,你可能要问了,加入出现在不同组的同一个邮箱,那岂不是又回分派在其他图,从而合并了吗?不会,可以看看上一步,在创建email_to_index的映射的时候,已经将重复的邮箱去掉了,只要有一个邮箱出现在别人的账号之中,那么,这个邮箱就会被忽略。这样,我们就可以进行合并了。可能你又要问,那出与重复邮箱出现在同一个组的其他邮箱怎么办?我们在想一想并查集的作用,当我们先把重复的邮箱放进一个连通图之后,我们只要按照循环来将其伙伴与重复邮箱合并,那么这整个组就会被另一个组合并,也就是同一个拥有者的所有邮箱。(可能语言描述的不太好理解,建议看看代码)
- 通过以上的步骤,将email按照拥有者构造好了连通图,按照题目要求,我们要返回不同拥有者以及其所有的邮箱集合。可以按照并查集里序号再来构造一个index_to_email,这里的index是置一个连通图中的根节点(通过这个邮箱可以访问到所有邮箱),这样的话,我们就可以按照以下逻辑:
email_1->email_1_index->uf.Find(email_1_index)->email_root_index->email_root->name
建立起待查的邮箱以及其拥有者的联系。但是按照以上逻辑,还必须建立起email_to_name的一个容器,不然无法根据email去找到拥有者的姓名。
**综上:**我们一共需要创建三个容器映射:email_to_name,email_to_index,index_to_email
代码
class Solution {
public:
struct UF
{
vector<int> uf;
UF(int num)
{
uf.resize(num);
for(int i=0;i<num;++i)
{
uf[i] = i;
}
}
int Find(int index)
{
if(uf[index]!=index) uf[index]=Find(uf[index]);
return uf[index];
}
void Union(int index1,int index2)
{
int found_1 = Find(index1);
int found_2 = Find(index2);
if(found_1==found_2) return;
uf[found_1] = found_2;
return;
}
};
vector<vector<string>> accountsMerge(vector<vector<string>>& accounts) {
map<string,int> email_to_index;
map<string,string> email_to_name;
int num = accounts.size(),index=0;
//1.将email分别映射到index和name
for(int i=0;i<num;++i)
{
string name = accounts[i][0];
for(int j=1;j<accounts[i].size();++j)
{
string email = accounts[i][j];
//查看是否已经包含,没有的话则分配新的id,同时将它指向对应的name。如果有的话,就说明已经出现在前面,是已经创建过的
if(!email_to_index.count(email))
{
email_to_index[email] = index++;
email_to_name[email] = name;
}
}
}
//2.构造并查集,根据同一个name里面进行合并,一个连通图就是一个姓名的所有email,这里的是所有不同的个数
UF uf(index);
for(int i=0;i<num;++i)
{
//先获取第一个email以及其的当前对应序号(一个email一个index,不会出现相同的)
string first_email = accounts[i][1];
int first_index = email_to_index[first_email];
//将同一个姓名之内的进行union;
for(int j=2;j<accounts[i].size();++j)
{
string second_email = accounts[i][j];
int second_index = email_to_index[second_email];
uf.Union(second_index, first_index);
}
}
//3.通过前两步,此时的连通图就是代表着一个人所拥有的所有email,因此,需要通过这去查找
map<int,vector<string>> index_to_email;
//通过Find函数,将拥有相同name的email组合在一起,并且可以通过map的int去查找该name
for(map<string,int>::iterator it=email_to_index.begin();it!=email_to_index.end();++it)
{
index_to_email[uf.Find(it->second)].push_back(it->first);
}
vector<vector<string>> result;
for(map<int,vector<string>>::iterator it = index_to_email.begin();it!=index_to_email.end();++it)
{
sort(it->second.begin(),it->second.end());
string name = email_to_name[it->second[0]];
it->second.insert(it->second.begin(),name);
result.push_back(it->second);
}
return result;
}
};