并查集

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. 1 <= grid.length == grid[0].length <= 30
  2. 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;
    }
};

 

posted @ 2020-04-27 22:22  鳄鱼四驱车  阅读(144)  评论(0编辑  收藏  举报