zoukankan      html  css  js  c++  java
  • 【LeetCode-并查集】朋友圈

    题目描述

    班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。

    给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。

    示例:

    输入:
    [[1,1,0],
     [1,1,0],
     [0,0,1]]
    输出:2 
    解释:已知学生 0 和学生 1 互为朋友,他们在一个朋友圈。
    第2个学生自己在一个朋友圈。所以返回 2 。
    
    输入:
    [[1,1,0],
     [1,1,1],
     [0,1,1]]
    输出:1
    解释:已知学生 0 和学生 1 互为朋友,学生 1 和学生 2 互为朋友,所以学生 0 和学生 2 也是朋友,所以他们三个在一个朋友圈,返回 1 。
    

    说明:

    • 1 <= N <= 200
    • M[i][i] == 1
    • M[i][j] == M[j][i]

    题目链接: https://leetcode-cn.com/problems/friend-circles/

    思路1

    使用 dfs。这题和求连通块不太一样。在 dfs 的时候,假设当前位置为 (r, c),则将 r 所在的那一行中所有的值为 1 的位置都 dfs,因为这些都是 r 朋友;同样地,也要把 c 所在的那一列中值为 1 的位置都 dfs。代码如下:

    class Solution {
    public:
        int findCircleNum(vector<vector<int>>& M) {
            if(M.empty() || M[0].empty()) return 0;
            int m = M.size();
            int n = M[0].size();
            int ans = 0;
            for(int i=0; i<m; i++){
                for(int j=0; j<n; j++){
                    if(M[i][j]==1){
                        ans++;
                        dfs(M, i, j);
                    }
                }
            }
            return ans;
        }
    
        void dfs(vector<vector<int>>& matrix, int r, int c){
            matrix[r][c] = 2;
            for(int i=0; i<matrix.size(); i++){ // 注意 i 是从 0 开始
                if(matrix[i][c]==1){
                    dfs(matrix, i, c);
                }
            }
            for(int j=0; j<matrix[0].size(); j++){  // 注意 j 是从 0 开始
                if(matrix[r][j]==1){
                    dfs(matrix, r, j);
                }
            }
        }
    };
    

    也可以将 dfs 中的两个循环写在一起:

    class Solution {
    public:
        int findCircleNum(vector<vector<int>>& M) {
            if(M.empty() || M[0].empty()) return 0;
    
            int ans = 0;
            for(int i=0; i<M.size(); i++){
                for(int j=0; j<M[0].size(); j++){
                    if(M[i][j]==1){
                        ans++;
                        dfs(M, i, j);
                    }
                }
            }
            return ans;
        }
    
        void dfs(vector<vector<int>>& matrix, int r, int c){
            matrix[r][c] = 2;
    
            for(int i=0; i<matrix.size(); i++){
                if(matrix[r][i]==1){
                    dfs(matrix, r, i);
                }
    
                if(matrix[i][c]==1){
                    dfs(matrix, i, c);
                }
            }
        }
    };
    

    思路2

    使用并查集。并查集共有一个数据结构和两个操作:

    • 一个数据结构:
      • parent[x]:x 的父节点,初始化为 -1,表示 x 为根节点,无父节点;
    • 两个操作:
      • find(x):查找 x 的祖先节点;
      • union(x, y):将 x 所在的树和 y 所在的树连接起来,可以用 find 操作分别找到 x 的祖先 xp 和 y 的祖先 yp,然后令 parent[xp] = yp 就可以将两棵树连起来;

    在这题中,如果 M[i][j] == 1,我们就把 i,j 放到一棵树中,也就是 union(i, j),两个节点在一棵树中说明这两个节点是朋友。然后统计 parent 中值为 -1 的个数,也就是有几棵树,返回 -1 的个数。代码如下:

    class Solution {
    public:
        int findCircleNum(vector<vector<int>>& M) {
            if(M.empty() || M[0].empty()) return 0;
    
            int n = M.size();
            vector<int> parent(n, -1);
            for(int i=0; i<n; i++){
                for(int j=0; j<n; j++){
                    if(M[i][j]==1 && i!=j){
                        unionn(parent, i, j);
                    }
                }
            }
    
            int cnt = 0;
            for(int i=0; i<parent.size(); i++){
                if(parent[i]==-1) cnt++;
            }   
            return cnt;
        }
    
        int find(vector<int>& parent, int i){
            if(parent[i]==-1) return i;
            return find(parent, parent[i]);
        }
    
        void unionn(vector<int>& parent, int i, int j){
            int ip = find(parent, i);
            int jp = find(parent, j);
            if(ip!=jp){
                parent[ip] = jp;
            }
        }
    };
    

    思路3

    思路 2 中的树最坏情况下会退化成一棵链表,我们可以在 find 的过程中进行路径压缩,使得每个节点 x 直接指向其祖先节点。代码如下:

    class Solution {
    public:
        int findCircleNum(vector<vector<int>>& M) {
            if(M.empty() || M[0].empty()) return 0;
    
            int n = M.size();
            vector<int> parent(n, -1);
            for(int i=0; i<n; i++){
                for(int j=0; j<n; j++){
                    if(M[i][j]==1 && i!=j){
                        unionn(parent, i, j);
                    }
                }
            }
    
            int cnt = 0;
            for(int i=0; i<parent.size(); i++){
                if(parent[i]==-1) cnt++;
            }   
            return cnt;
        }
    
        int find(vector<int>& parent, int i){
            if(parent[i]==-1) return i;
            return parent[i] = find(parent, parent[i]);  // 只改了这一句
        }
        
        // 这样写更好理解
        /*int find(vector<int>& parent, int i){
            if(parent[i]==-1) return i;
    
            int root = find(parent, parent[i]);
            parent[i] = root;
            return root;
        }*/
    
        void unionn(vector<int>& parent, int i, int j){
            int ip = find(parent, i);
            int jp = find(parent, j);
            if(ip!=jp){
                parent[ip] = jp;
            }
        }
    };
    

    也可以将 find 换成迭代的形式:

    class Solution {
    public:
        int findCircleNum(vector<vector<int>>& M) {
            if(M.empty() || M[0].empty()) return 0;
    
            int n = M.size();
            vector<int> parent(n, -1);
            for(int i=0; i<n; i++){
                for(int j=0; j<n; j++){
                    if(M[i][j]==1 && i!=j){
                        unionn(parent, i, j);
                    }
                }
            }
    
            int cnt = 0;
            for(int i=0; i<parent.size(); i++){
                if(parent[i]==-1) cnt++;
            }   
            return cnt;
        }
    
        int find(vector<int>& parent, int i){
            int t = i;
            while(parent[i]!=-1){
                i = parent[i];
            }
            while(parent[t]!=-1){
                int p = parent[t];
                parent[t] = i;
                t = p;
            }
            return i;
        }
    
        void unionn(vector<int>& parent, int i, int j){
            int ip = find(parent, i);
            int jp = find(parent, j);
            if(ip!=jp){
                parent[ip] = jp;
            }
        }
    };
    

    参考

    1、https://leetcode-cn.com/problems/friend-circles/solution/peng-you-quan-by-leetcode/
    2、https://leetcode-cn.com/problems/friend-circles/solution/java-bing-cha-ji-lu-jing-ya-suo-zhi-xing-yong-shi-/
    3、https://labuladong.gitbook.io/algo/gao-pin-mian-shi-xi-lie/unionfind-suan-fa-xiang-jie

  • 相关阅读:
    【POJ 1958】 Strange Towers of Hanoi
    【HNOI 2003】 激光炸弹
    【POJ 3263】 Tallest Cow
    【POJ 2689】 Prime Distance
    【POJ 2777】 Count Color
    【POJ 1995】 Raising Modulo Numbers
    【POJ 1845】 Sumdiv
    6月16日省中集训题解
    【TJOI 2018】数学计算
    【POJ 1275】 Cashier Employment
  • 原文地址:https://www.cnblogs.com/flix/p/13580054.html
Copyright © 2011-2022 走看看