算法-并查集/DFS/BFS-朋友圈

算法-并查集/DFS/BFS-朋友圈

1 题目概述

1.1 题目出处

https://leetcode-cn.com/problems/friend-circles/

1.2 题目描述

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

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

示例 1:

输入:
[[1,1,0],
[1,1,0],
[0,0,1]]
输出: 2
说明:已知学生0和学生1互为朋友,他们在一个朋友圈。
第2个学生自己在一个朋友圈。所以返回2。
示例 2:

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

注意:

  • N 在[1,200]的范围内。
  • 对于所有学生,有M[i][i] = 1。
  • 如果有M[i][j] = 1,则有M[j][i] = 1。

2 并查集

2.1 思路

遍历每个学生,并从该学生的下一个序号开始处理,只要值为1代表两个学生为朋友,此时就进行并查集合并代表同属一个朋友圈。

最后返回并查集内不同root祖先个数,即得到朋友圈个数。

2.2 代码

class Solution {
    public int findCircleNum(int[][] M) {
        if(M == null || M.length == 0){
            return 0;
        }
        // 并查集,大小为学生个数
        int[] unionFindSet = new int[M.length];

        // 初始化并查集值为元素序号本身
        for(int i = 1; i < unionFindSet.length; i++){
            unionFindSet[i] = i;
        }

        // 按行遍历每个学生
        for(int i = 0; i < unionFindSet.length; i++){
            // 从每个学生的下一个序号开始遍历处理朋友关系
            for(int j = i + 1; j < unionFindSet.length; j++){
                // 只要值为1,代表i学生和j号学生是朋友,则将他们的并查集合并
                if(M[i][j] == 1){
                    join(unionFindSet, i, j);
                }
            }
        }

        // 用来统计朋友圈的HashSet
        Set<Integer> filterSet = new HashSet<>();

        // 遍历并查集,看不同的root祖先有几个就知道有几个不相连的并查集
        // 即有几个朋友圈
        for(int id : unionFindSet){
            filterSet.add(search(unionFindSet, id));
        }

        return filterSet.size();
    }

    /**
     * 将两个元素的祖先节点所在并查集合并
     */
    private void join(int[] unionFindSet, int p, int q){
        //
        int left = search(unionFindSet,p);
        int right = search(unionFindSet,q);
        if (left == right){
            // 同一并查集
            return;
        }
        // 将p元素的root祖先节点的祖先设为q元素的root祖先节点
        // 这样就合并为了一个并查集
        unionFindSet[left] = right;
    }

    /**
     * 寻找并查集root祖先
     */
    private int search(int[] unionFindSet, int p){
        int son = p;
        // 从当前节点开始迭代查找祖先
        // 直到某个元素祖先节点是自己,那就是找到了root祖先元素
        while(p != unionFindSet[p]){
            p = unionFindSet[p];
        }
        // 路径压缩算法,祖先的祖先全部设为p,高度变为2
        while(son != p){
            int tmp = unionFindSet[son];
            unionFindSet[son] = p;
            son = tmp;
        }
        // 返回root祖先元素
        return p;
    }
}

2.3 时间复杂度

在这里插入图片描述

2.4 空间复杂度

O(N)

3 图-DFS

3.1 思路

按行遍历学生,只要没访问过就说明属于一个新的独立朋友圈,count加1。此时,开始从下标为i+1的开始遍历其他学生j,只要M[i,j] == 1 说明是朋友,开始对学生j dfs。一趟dfs中遍历到的学生都属于这次i学生的朋友圈,只计数1次。

最后返回count即可。

3.2 代码

class Solution {
    public int findCircleNum(int[][] M) {
        if(M == null || M.length == 0){
            return 0;
        }
        // 朋友圈个数
        int count = 0;

        // 记录是否已遍历
        int[] visited = new int[M.length];
        
        // 按行遍历每个学生
        for(int i = 0; i < M.length; i++){
            if(visited[i] == 1){
                continue;
            }
            // 没人访问过该学生,说明该学生目前是个独立朋友圈
            // 那么此时遍历到的关系为1的学生以及由此进行dfs访问到的学生同属i学生的一个朋友圈
            count++;
            // 标记该学生已访问过
            visited[i] = 1;
            // 从每个学生的下一个序号开始遍历处理朋友关系
            for(int j = i + 1; j < M.length; j++){
                // 只要值为1,代表i学生和j号学生是朋友,则将他们的朋友圈合并
                if(visited[j] == 0 && M[i][j] == 1){
                    dfs(M, j, visited);
                }
            }
        }

        return count;
    }

    /**
     * i作为朋友圈代号
     * j为当前遍历学生序号
     */
    private void dfs(int[][] M, int j, int[] visited){
        visited[j] = 1;
        // 这里注意要出0开始,因为有可能还没访问过的、序号更小的节点和当前节点是朋友
        // 原因是采用了DFS,无顺序性!!
        for(int k = 0; k < M.length; k++){
            // 只要值为1,代表j学生和k号学生是朋友,则将他们的朋友圈合并
            if(visited[k] == 0 && M[j][k] == 1){
                dfs(M, k, visited);
            }
        }
    }
}

3.3 时间复杂度

在这里插入图片描述

3.4 空间复杂度

O(N)

  • 因为需要递归

4 BFS

4.1 思路

4.2 代码


4.3 时间复杂度

参考文档

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章