给定一个列表 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]的范围内。
class Solution {
unordered_map<string,string>parent;
unordered_map<string,int>rank;
unordered_map<string,list<string>>traverseChild;//list merge合并排序
unordered_map<string,int>mailToNameIndex;
public:
vector<vector<string>> accountsMerge(vector<vector<string>>& accounts) {
int m=accounts.size();
for(int i=0;i<m;++i){
mailToNameIndex[accounts[i][1]]=i;
Find(accounts[i][1]);
for(int j=2;j<accounts[i].size();++j){
mailToNameIndex[accounts[i][j]]=i;//mai2name
Union(accounts[i][1],accounts[i][j]);
}
}
vector<vector<string>>res;
for(auto &t:traverseChild){
vector<string>temp;
temp.push_back(accounts[mailToNameIndex[t.first]][0]);
for(auto &l:t.second)
temp.push_back(l);
res.push_back(temp);
}
return res;
}
string Find(string x){
if(parent.count(x))
return x==parent[x]?x:(parent[x]=Find(parent[x]));
else{
rank[x]=1;
traverseChild[x].push_back(x);
return (parent[x]=x);
}
}
void Union(string a,string b){
string fa=Find(a),fb=Find(b);
if(fa==fb)return;
if(rank[fa]<rank[fb]){
parent[fa]=fb;
traverseChild[fb].merge(traverseChild[fa]);
traverseChild.erase(fa);
}
else if(rank[fa]>rank[fb]){
parent[fb]=fa;
traverseChild[fa].merge(traverseChild[fb]);
traverseChild.erase(fb);
}
else{
parent[fb]=fa;
traverseChild[fa].merge(traverseChild[fb]);
traverseChild.erase(fb);
++rank[fa];
}
}
};
我们将石头放置在二维平面中的一些整数坐标点上。每个坐标点上最多只能有一块石头。
每次 move 操作都会移除一块所在行或者列上有其他石头存在的石头。
请你设计一个算法,计算最多能执行多少次 move 操作?
示例 1:
输入:stones = [[0,0],[0,1],[1,0],[1,2],[2,1],[2,2]]
输出:5
示例 2:
输入:stones = [[0,0],[0,2],[1,1],[2,0],[2,2]]
输出:3
示例 3:
输入:stones = [[0,0]]
输出:0
提示:
1 <= stones.length <= 1000
0 <= stones[i][j] < 10000
class UnionFind{
unordered_map<int,int>parent,rank,size;
int maxSize;
public:
UnionFind(vector<vector<int>>& stones){
for(int i=0;i<stones.size();++i){
int index=stones[i][0]*10000+stones[i][1];
parent[index]=index;
rank[index]=1;
size[index]=1;
}
maxSize=0;
}
int Find(int x){
return x==parent[x]?x:parent[x]=Find(parent[x]);
}
void Union(int a,int b){
int fa=Find(a),fb=Find(b);
if(fa==fb)return;
if(rank[fa]<rank[fb]){
parent[fa]=fb;
size[fb]+=size[fa];
size[fa]=0;
}
else if(rank[fa]>rank[fb]){
parent[fb]=fa;
size[fa]+=size[fb];
size[fb]=0;
}
else{
parent[fb]=fa;
size[fa]+=size[fb];
size[fb]=0;
++rank[fa];
}
}
int getMaxSize(){//每个集合的个数-1的和就是答案,也等于stones.size()-集合个数cnt
for(auto &i:size)
if(i.second)maxSize+=(i.second-1);
return maxSize;
}
};
class Solution {
public:
int removeStones(vector<vector<int>>& stones) {
unordered_map<int,int>row,col;//同行/同列合并
UnionFind uf(stones);
for(int i=0;i<stones.size();++i){
if(row.count(stones[i][0])==0&&col.count(stones[i][1])==0){
row[stones[i][0]]=stones[i][0]*10000+stones[i][1];
col[stones[i][1]]=row[stones[i][0]];
continue;
}
if(row.count(stones[i][0])){
uf.Union(row[stones[i][0]],stones[i][0]*10000+stones[i][1]);
if(col.count(stones[i][1]))
uf.Union(col[stones[i][1]],stones[i][0]*10000+stones[i][1]);
else
col[stones[i][1]]=row[stones[i][0]];
}
else{
uf.Union(col[stones[i][1]],stones[i][0]*10000+stones[i][1]);
row[stones[i][0]]=col[stones[i][1]];
}
}
return uf.getMaxSize();
}
};
算法
对于一个坐标为 (i, j) 的石子来说,需要把行 i 和列 j 合并,因为并查集是一维的,用 j+10000 来代替 j。在将所有石子的行和列都合并好之后,只需数一下并查集中有几个集合就可以得到答案了。
class UnionFind{
unordered_map<int,int>parent,rank;
int cnt,initSize;
public:
UnionFind(vector<vector<int>>& stones){
initSize=stones.size();
for(int i=0;i<initSize;++i){
//行
int index=stones[i][0];
parent[index]=index;
rank[index]=1;
//列
index=10000+stones[i][1];
parent[index]=index;
rank[index]=1;
}
cnt=parent.size();
}
int Find(int x){
return x==parent[x]?x:parent[x]=Find(parent[x]);
}
void Union(int a,int b){
int fa=Find(a),fb=Find(b);
if(fa==fb)return;
if(rank[fa]<rank[fb])
parent[fa]=fb;
else if(rank[fa]>rank[fb])
parent[fb]=fa;
else{
parent[fb]=fa;
++rank[fa];
}
--cnt;
}
int getAns(){
return initSize-cnt;
}
};
class Solution {
public:
int removeStones(vector<vector<int>>& stones) {
unordered_map<int,int>row,col;
UnionFind uf(stones);
for(int i=0;i<stones.size();++i)
uf.Union(stones[i][0],stones[i][1]+10000);
return uf.getAns();
}
};
在由 1 x 1 方格组成的 N x N 网格 grid 中,每个 1 x 1 方块由 /、\ 或空格构成。这些字符会将方块划分为一些共边的区域。
(请注意,反斜杠字符是转义的,因此 \ 用 "\\" 表示。)。
返回区域的数目。
示例 1:
输入:
[
" /",
"/ "
]
输出:2
解释:2x2 网格如下:
示例 2:
输入:
[
" /",
" "
]
输出:1
解释:2x2 网格如下:
示例 3:
输入:
[
"\\/",
"/\\"
]
输出:4
解释:(回想一下,因为 \ 字符是转义的,所以 "\\/" 表示 \/,而 "/\\" 表示 /\。)
2x2 网格如下:
示例 4:
输入:
[
"/\\",
"\\/"
]
输出:5
解释:(回想一下,因为 \ 字符是转义的,所以 "/\\" 表示 /\,而 "\\/" 表示 \/。)
2x2 网格如下:
示例 5:
输入:
[
"//",
"/ "
]
输出:3
解释:2x2 网格如下:
提示:
1 <= grid.length == grid[0].length <= 30
grid[i][j] 是 '/'、'\'、或 ' '。
class UnionFind{
vector<int>parent,rank;//指向父节点,秩/高度
int cnt;//集合内部环个数
public:
UnionFind(int n){
cnt=1;
int N=pow(n,2);
parent.resize(N,0);
rank.resize(N,1);
rank[0]=2;
for(int i=1;i<n-1;++i)
for(int j=1;j<n-1;++j)
parent[i*n+j]=i*n+j;
}
int Find(int x){
return x==parent[x]?x:(parent[x]=Find(parent[x]));
}
void Union(int a,int b){
int fa=Find(a),fb=Find(b);
if(fa==fb){
++cnt;
return;
}
if(rank[fa]>rank[fb])
parent[fb]=fa;
else if(rank[fa]<rank[fb])
parent[fa]=fb;
else{
parent[fa]=fb;
++rank[fb];
}
}
int getCnt(){
return cnt;
}
};
class Solution {//方法一,并查集:合并对象是网格点坐标,不是网格!!!
public:
int regionsBySlashes(vector<string>& grid) {//易错点:n的大小问题
int n=grid.size()+1;
UnionFind uf(n);//二维点[0~n][0~n],四周边上的点是同一个集合
for(int i=0;i<n-1;++i){
for(int j=0;j<n-1;++j){
if(grid[i][j]=='\\')// \对应两点[i][j] [i+1][j+1]
uf.Union(i*n+j,(i+1)*n+j+1);
else if(grid[i][j]=='/')// /对应两点[i][j+1] [i+1][j]
uf.Union(i*n+j+1,(i+1)*n+j);
}
}
return uf.getCnt();
}
};
class UnionFind{
vector<int>parent,rank;//指向父节点,秩/高度
int cnt;//集合个数
public:
UnionFind(vector<vector<int>>& Grid){
int n=Grid.size();
cnt=0;
parent.resize(n*n,-1);
rank.resize(n*n,1);
for(int i=0;i<n;++i)
for(int j=0;j<n;++j)
if(Grid[i][j]==1)
parent[i*n+j]=i*n+j,++cnt;
}
int Find(int x){
return x==parent[x]?x:(parent[x]=Find(parent[x]));
}
void Union(int a,int b){
int fa=Find(a),fb=Find(b);
if(fa==fb)
return;
if(rank[fa]>rank[fb])
parent[fb]=fa;
else if(rank[fa]<rank[fb])
parent[fa]=fb;
else{
parent[fa]=fb;
++rank[fb];
}
--cnt;
}
int getCnt(){
return cnt;
}
};
class Solution {//方法二,并查集:并查对象是扩大三倍后的网格,不是网格线!!!(速度很慢)
public:
int regionsBySlashes(vector<string>& grid) {
int n=grid.size();
int N=3*n;
vector<vector<int>>Grid(N,vector<int>(N,1));//相连为1表示连通
for(int ii=0;ii<n;++ii)
for(int jj=0;jj<n;++jj)
if(grid[ii][jj]=='\\'){
int i=3*ii,j=3*jj;
Grid[i][j]=Grid[i+1][j+1]=Grid[i+2][j+2]=0;//为0表示分界线
}
else if(grid[ii][jj]=='/'){
int i=3*ii,j=3*jj;
Grid[i][j+2]=Grid[i+1][j+1]=Grid[i+2][j]=0;
}
UnionFind uf(Grid);
for(int i=0;i<N;++i)
for(int j=0;j<N;++j)
if(Grid[i][j]==1){//上下左右和当前节点都为不为分界线则合并
if(i<N-1&&Grid[i+1][j]==1)uf.Union(i*N+j,(i+1)*N+j);
if(j<N-1&&Grid[i][j+1]==1)uf.Union(i*N+j,i*N+j+1);
if(i>0&&Grid[i-1][j]==1)uf.Union(i*N+j,(i-1)*N+j);
if(j>0&&Grid[i][j-1]==1)uf.Union(i*N+j,i*N+j-1);
}
return uf.getCnt();
}
};