算法-并查集/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;
    }

    /**
     * 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 思路

按行遍历学生,只要没访问过就说明属于一个新的独立朋友圈,count加1。此时,将下标i放入队列,进行一趟bfs。

bfs内不断从队列中取int值 i,并从0开始遍历所有学生,只要没访问过且M[i,j] == 1 说明是朋友,将学生j也放入队列。一趟bfs结束条件为队列为空。

最后返回count即可。

4.2 代码

class Solution {
    private LinkedList<Integer> queue = new LinkedList<>();
    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++;
            queue.add(i);
            // 开始bfs
            bfs(M, visited);
        }
        return count;
    }

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

4.3 时间复杂度

在这里插入图片描述

4.4 空间复杂度

O(N)

参考文档

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值