并查集
547. 朋友圈
班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。
给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。
常规思路,看有多少个连通图,可以用BFS也可以用DFS
void BFS(vector<vector<int>>& M, int st, vector<bool>& vis){ queue<int> q; q.push(st); vis[st]=1; while(!q.empty()){ int t=q.front();q.pop(); for(int i=0;i<M.size();i++){ if(M[t][i]==1&&!vis[i]){ q.push(i); vis[i]=1; } } } } int findCircleNum(vector<vector<int>>& M) { int n=M.size(); if(n==0)return 0; vector<bool> vis(n,false); int ans=0; for(int i=0;i<n;i++){ if(!vis[i]){ ans++; BFS(M,i,vis); } } return ans; }
用并查集
int findCircleNum(vector<vector<int>>& M) { int n=M.size(); if(n==0)return 0; vector<int> parent(n,-1); int ans=0; for(int i=0;i<n;i++){ for(int j=i+1;j<n;j++){ if(M[i][j]==1){ int a=i,b=j; while(parent[a]!=-1)a=parent[a]; while(parent[b]!=-1)b=parent[b]; if(a!=b)parent[b]=a; } } } for(int i=0;i<n;i++){ if(parent[i]==-1)ans++; } return ans; }
684. 冗余连接
在本问题中, 树指的是一个连通且无环的无向图。
输入一个图,该图由一个有着N个节点 (节点值不重复1, 2, ..., N) 的树及一条附加的边构成。附加的边的两个顶点包含在1到N中间,这条附加的边不属于树中已存在的边。
结果图是一个以边组成的二维数组。每一个边的元素是一对[u, v] ,满足 u < v,表示连接顶点u 和v的无向图的边。
返回一条可以删去的边,使得结果图是一个有着N个节点的树。如果有多个答案,则返回二维数组中最后出现的边。答案边 [u, v] 应满足相同的格式 u < v。
vector<int> findRedundantConnection(vector<vector<int>>& edges) { int N=edges.size(); vector<int> parent(N+1,-1); for(int i=0;i<N+1;i++){ int a=edges[i][0],b=edges[i][1]; while(parent[a]!=-1)a=parent[a]; while(parent[b]!=-1)b=parent[b]; if(a!=b)parent[b]=a; else return {edges[i][0],edges[i][1]}; } return {-1,-1}; }
721. 账户合并
给定一个列表 accounts,每个元素 accounts[i] 是一个字符串列表,其中第一个元素 accounts[i][0] 是 名称 (name),其余元素是 emails 表示该帐户的邮箱地址。
现在,我们想合并这些帐户。如果两个帐户都有一些共同的邮件地址,则两个帐户必定属于同一个人。请注意,即使两个帐户具有相同的名称,它们也可能属于不同的人,因为人们可能具有相同的名称。一个人最初可以拥有任意数量的帐户,但其所有帐户都具有相同的名称。
合并帐户后,按以下格式返回帐户:每个帐户的第一个元素是名称,其余元素是按顺序排列的邮箱地址。accounts 本身可以以任意顺序返回。
class DSU{ vector<int> parent; public: DSU(){ parent = vector<int>(10001,0); for(int i=0;i<=10000;i++) parent[i]=i; } int find(int x){ if(parent[x]!=x) parent[x]=find(parent[x]); return parent[x]; } void un(int x, int y){ parent[find(x)]=find(y); } }; class Solution { public: vector<vector<string>> accountsMerge(vector<vector<string>>& accounts) { DSU dsu; map<string, string> emailsToName; map<string, int> emailsToId; int id = 0; for(auto& account:accounts){ string name=""; for(auto& mail:account){ if(name==""){ name=mail; continue; } if(emailsToName.count(mail)==0) emailsToName.insert({mail,name}); if(emailsToId.count(mail)==0)emailsToId[mail]=id++; dsu.un(emailsToId[account[1]],emailsToId[mail]); } } unordered_map<int, vector<string>> tmp; vector<vector<string>> ans; for(auto& mtn : emailsToName){ string mail = mtn.first;//cout<<mail<<" "; int idx=dsu.find(emailsToId[mail]); if(tmp.count(idx)>0) tmp[idx].push_back(mail); else tmp.insert({idx,{mtn.second,mail}}); } for(auto& list : tmp){ sort(list.second.begin()+1,list.second.end()); vector<string> v(list.second.begin(),list.second.end()); ans.push_back(v); } return ans; } };
959. 由斜杠划分区域(这道题目我很喜欢!)
在由 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]
是'/'
、'\'
、或' '
。
我们将N*N的大正方形,划分为N*N个1*1的小正方形,因为'\'和'/'只能将一个正方形划分为四个小三角形,我们以上方的小三角形为0,顺时针编号0,1,2,3。
那么当方格内为'\'时,区域0和1相连、2和3相连;当方格内为'/'时,0和3相连、1和2相连。同时左右相邻的小方格1和3相连,上下相邻的小方格0和2相连。
class Union{ vector<int> parent; public: Union(int n){ parent=vector<int>(n,0); for(int i=0;i<n;i++) parent[i]=i; } int find(int x){ if(parent[x]!=x) parent[x]=find(parent[x]); return parent[x]; } void un(int x, int y){ parent[find(x)]=find(y); } }; class Solution { public: int regionsBySlashes(vector<string>& grid) { int n=grid.size(); Union uf(n*n*4); for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ int start=i*n*4+j*4; switch(grid[i][j]){ case '\\':{ uf.un(start+1,start); uf.un(start+2,start+3); }break; case '/':{ uf.un(start+3,start); uf.un(start+2,start+1); }break; case ' ':{ uf.un(start,start+1); uf.un(start+1,start+2); uf.un(start+2,start+3); }break; default:break; } if(i>0){ uf.un(start,start-n*4+2); } if(j>0){ uf.un(start+3,start-3); } } } int ans=0; for(int i=0;i<n*n*4;i++){ if(uf.find(i)==i)++ans; } return ans; } };
399. 除法求值
给出方程式 A / B = k
, 其中 A
和 B
均为代表字符串的变量, k
是一个浮点型数字。根据已知方程式求解问题,并返回计算结果。如果结果不存在,则返回 -1.0
。
示例 :
给定 a / b = 2.0, b / c = 3.0
问题: a / c = ?, b / a = ?, a / e = ?, a / a = ?, x / x = ?
返回 [6.0, 0.5, -1.0, 1.0, -1.0 ]
输入为: vector<pair<string, string>> equations, vector<double>& values, vector<pair<string, string>> queries
(方程式,方程式结果,问题方程式), 其中 equations.size() == values.size()
,即方程式的长度与方程式结果长度相等(程式与结果一一对应),并且结果值均为正数。以上为方程式的描述。 返回vector<double>
类型。
基于上述例子,输入如下:
equations(方程式) = [ ["a", "b"], ["b", "c"] ], values(方程式结果) = [2.0, 3.0], queries(问题方程式) = [ ["a", "c"], ["b", "a"], ["a", "e"], ["a", "a"], ["x", "x"] ].
输入总是有效的。你可以假设除法运算中不会出现除数为0的情况,且不存在任何矛盾的结果。
学习了一天的并查集是有收获的!
先把字符串映射为int数字x
res[x]保存x所在的集合的根r/x的值
对于每一个表达式 x/y 每次合并的时候分情况讨论一下,计算出res[x],res[y]的值
最后路径压缩的时候注意要更新一下res[x]因为存在输入为a<-b c<-e b->c的情况 ,这个时候res[e]保存的仍然是c/e的值,但是路径压缩e直接指向a就会更新为a/e
class Union{ vector<int> parent; vector<double> res; public: Union(int n){ parent=vector<int>(n,0); for(int i=0;i<n;i++) parent[i]=i; res=vector<double>(n,0.0); } int find(int x){ if(parent[x]!=x) { res[x]=res[x]*res[parent[x]]; parent[x]=find(parent[x]); } return parent[x]; } void un(int x, int y, double value){ int a=find(x); int b=find(y); if(a==x&&b==y){ res[x]=1.0; res[y]=value; parent[y]=x; } else if(a==x){ parent[x]=b; res[x]=res[y]/value; } else if(b==y){ parent[y]=a; res[y]=res[x]*value; } else{ parent[b]=a; res[y]=res[x]*value; res[b]=res[x]/res[y]; } } double getRes(int x,int y){ int a=find(x); int b=find(y); if(a!=b)return -1.0; else return res[y]/res[x]; } }; class Solution { public: vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) { int n=equations.size(); Union uf(n*2); unordered_map<string, int> strId; unordered_map<string, string> divide; int id=0; for(int i=0;i<equations.size();i++){ if(strId.count(equations[i][0])==0){ strId.insert({equations[i][0],id++}); } if(strId.count(equations[i][1])==0){ strId.insert({equations[i][1],id++}); } uf.un(strId[equations[i][0]],strId[equations[i][1]],values[i]); } vector<double> ans; for(auto& equ:queries){ if(strId.count(equ[0])==0||strId.count(equ[1])==0) ans.push_back(-1.0); else{ ans.push_back(uf.getRes(strId[equ[0]],strId[equ[1]])); } } return ans; } };
765. 情侣牵手
N 对情侣坐在连续排列的 2N 个座位上,想要牵到对方的手。 计算最少交换座位的次数,以便每对情侣可以并肩坐在一起。 一次交换可选择任意两人,让他们站起来交换座位。
人和座位用 0 到 2N-1 的整数表示,情侣们按顺序编号,第一对是 (0, 1),第二对是 (2, 3),以此类推,最后一对是 (2N-2, 2N-1)。
这些情侣的初始座位 row[i] 是由最初始坐在第 i 个座位上的人决定的。
class Union{ public: vector<int> parent; Union(int n){ parent=vector<int>(n, 0); for(int i=0;i<n;i++)parent[i]=i; } int find(int x){ if(parent[x]!=x) parent[x]=find(parent[x]); return parent[x]; } void un(int x, int y){ parent[find(x)]=find(parent[y]); } }; class Solution { public: int minSwapsCouples(vector<int>& row) { int n=row.size(); Union uf(n); for(int i=0;i<n-1;i+=2){ uf.un(row[i],row[i+1]); } for(int i=0;i<n-1;i+=2){ uf.un(i,i+1); } int ans=0; for(int i=0;i<n;i++){ if(uf.find(i)==i){ int cnt=0; for(int j=0;j<n;j++){ if(uf.find(j)==i)cnt++; } ans+=(cnt/2-1); } } return ans; } };