并查集合(力扣)

0. 基础知识

参考基础知识博客

1. 朋友圈问题

朋友圈

Nr.527

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

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

解题思路:
利用并查集合的思路,根据矩阵的大小初始化并查集合类,每个学生都有编号i[0~N-1],他们表示每个独立的节点。遍历朋友关系矩阵,当矩阵中M[i][j]=1时,就把对应的学生集合合并,最后返回sizeMap.size()就是朋友圈总数。

public static class UnionFindSet{
    // 记录每个节点的父节点,也就是头节点
    public HashMap<Integer, Integer> fatherMap;
    // 记录每个头节点的所挂节点的总个数
    public HashMap<Integer, Integer> sizeMap;

    // 初始化并查集合
    public UnionFindSet(int N){
        fatherMap = new HashMap<Integer, Integer>();
        sizeMap = new HashMap<Integer, Integer>();
        for (int i=0; i<N; i++){
            fatherMap.put(i, i);
            sizeMap.put(i ,1);
        }
    }

    // 找到给定的节点的父节点
    public int findFather(int node){
        // 把向上找的过程中,沿途的点都放在path里
        Deque<Integer> stack = new ArrayDeque<>();
        while (node != fatherMap.get(node)){
            stack.push(node);
            node = fatherMap.get(node);
        }
        // 沿途节点,更改父亲节点
        while (!stack.isEmpty()){
            fatherMap.put(stack.pop(), node);
        }
        return node;
    }
    // 判断两个节点是否是一个集合
    public boolean isSameSet(int a, int b){
        return findFather(a) == findFather(b);
    }
    // 合并给定的两个节点
    public void union(int a, int b){
        int aF = findFather(a);
        int bF = findFather(b);
        // 不在一个集合,需要合并
        if (aF != bF){
            // 小节点挂在大节点上
            int big = sizeMap.get(aF) >= sizeMap.get(bF) ? aF : bF;
            int small = big == aF ? bF : aF;
            // 修改small的节点,挂在big上
            fatherMap.put(small, big);
            // 接受两个链表的和
            sizeMap.put(big, sizeMap.get(big) + sizeMap.get(small));
            sizeMap.remove(small);
        }
    }

    // 查询最后父节点的数目,也就是合并后,不相通的集合数量
    public int getNum(){
        return sizeMap.size();
    }
}

public static void main(String[] args) {
    int [][] M1 = {{1,1,0},
                   {1,1,1},
                   {0,1,1}};

    UnionFindSet unionFindSet = new UnionFindSet(M1.length);
    for (int i=0; i<M1.length; i++){
        for (int j=0; j<M1.length; j++){
            if (i!=j && M1[i][j]==1){
                unionFindSet.union(i, j);
            }
        }
    }
    System.out.println(unionFindSet.getSize());
}

消息团队通知

腾讯笔试
大团队有n个人,m个小团队。每个小团队的人数和编号已知。向编号0发送一个通知,所有和编号0同属于一个小团队都会知道,小团队中的人还会告知他们所属的小团队。现在想知道最后会有多少个人直到通知?

输入:
第一行输入两个,团队总人数和小团队数目
剩下的行,第一个数是小团队的总人数,然后依次是小团队人员编号
50 5
2 1 2
5 10 11 12 13 14
2 0 1
2 49 2
4 6 7 8 2
返回: 7, 通知到的人是: 0 1 2 6 7 8 49

解题思路:
可以用并查集合解决,建立和初始化一个大小为总人数的并查集合。每次输入的一整行相互之间合并组成一个集合,后面的行(小团队)如果有之前团队成员的编号就会自动合并成更大的集合,最后统计编号0所在的父节点的sizeMap.size()就是最后的人数。

public static void main(String[] args) {

    Scanner sc = new Scanner(System.in);
    int n = sc.nextInt();
    int m = sc.nextInt();
    UnionFindSet unionFindSet = new UnionFindSet(n);
    List<List<Integer>> list = new ArrayList<>();
    while (m-- > 0){
        int groupLength = sc.nextInt();
        List<Integer> subList = new ArrayList<>();
        while (groupLength-- > 0){
            subList.add(sc.nextInt());
        }
        for (int i=1; i<subList.size(); i++){
            // 核心思想是: 合并两个节点,是合并节点所在的集合!
            unionFindSet.union(subList.get(0), subList.get(i));
        }
        list.add(subList);
    }
    sc.close();
    System.out.println(list);
    System.out.println(unionFindSet.sizeMap.get(unionFindSet.findFather(0)));
}

2. 岛屿问题

岛屿数量

Nr. 200

给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。

解题思路:
利用并查集合的思路,根据二维网格的大小初始化并查集合类,其中每个等于1的网格就是一个暂时独立的节点。在网格中遍历每个点,检查它的上下左右,如果有1就把它们所在的集合合并,最后岛屿的数量就是所有独立父节点的数量,也就是sizeMap.size()

public static class UnionFindSets{
    // 记录每个节点的父节点,也就是头节点
    public HashMap<Integer, Integer> fatherMap;
    // 记录每个头节点的所挂节点的总个数
    public HashMap<Integer, Integer> sizeMap;

    // 初始化并查集合
    public UnionFindSets(char[][] M){
        fatherMap = new HashMap<>();
        sizeMap = new HashMap<>();
        int rows = M.length;
        int cols = M[0].length;
        for (int i=0; i<rows; i++){
            for (int j=0; j<cols; j++){
                if (M[i][j] == '1'){
                    // 为每个1编号
                    int cur = i*cols + j;
                    fatherMap.put(cur, cur);
                    sizeMap.put(cur, 1);
                }
            }
        }
    }

    public int getFather(int node){
        Deque<Integer> stack = new ArrayDeque<>();
        // 收集沿路的节点
        while (node != fatherMap.get(node)){
            stack.push(node);
            node = fatherMap.get(node);
        }
        // 扁平化
        while (!stack.isEmpty()){
            fatherMap.put(stack.pop(), node);
        }
        return node;
    }

    // 判断两个节点是否是一个集合
    public boolean isSameSet(int a, int b){
        return getFather(a) == getFather(b);
    }

    public void union(int a, int b){
        int aF = getFather(a);
        int bF = getFather(b);
        // 不在一个集合,需要合并
        if (aF != bF){
            // 小节点挂在大节点上
            int big = sizeMap.get(aF) >= sizeMap.get(bF) ? aF : bF;
            int small = big == aF ? bF : aF;
            // 修改small的节点,挂在big上
            fatherMap.put(small, big);
            // 接受两个链表的和
            sizeMap.put(big, sizeMap.get(big) + sizeMap.get(small));
            sizeMap.remove(small);
        }
    }

    // 查询最后父节点的数目,也就是合并后,不相通的集合数量
    public int getSize(){
        return sizeMap.size();
    }
}

public static void main(String[] args) {

    UnionFindSets unionFindSet_1 = new UnionFindSets(grid);
    int rows = grid.length;
    int cols = grid[0].length;

    for (int i = 0; i < rows; i++){
        for (int j = 0; j < cols; j++){
            if (grid[i][j] == '1'){
                int position = i * cols + j;
                // 合并左下就可以完成检查
                if ( (i+1)<rows && grid[i+1][j] == '1'){
                    int down = (i+1) * rows + j;
                    unionFindSet_1.union(position, down);
                }

                // 合并左下就可以完成检查
                if ( (j+1)<cols && grid[i][j+1] == '1'){
                   int right = i * rows + j+1;
                    unionFindSet_1.union(position, right);
                }
            }
        }
    }
    System.out.println(unionFindSet_1.getSize());
}

岛屿的最大面积

Nr. 695
给定一个包含了一些 0 和 1 的非空二维数组 grid 。
一个岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0 。)

解题思路:
和岛屿数量一致,最后在sizeMap中遍历,找到最大的value即可.

// 获得sizeMap最大的value
public int getMaxSize(){
    int max = 0;
    for (int cur : sizeMap.keySet()){
        max = max > sizeMap.get(cur) ? max : sizeMap.get(cur);
    }
    return max;
}

全部评论

相关推荐

有趣的牛油果开挂了:最近这个阶段收到些杂七杂八的短信是真的烦
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务