Union Find 题型总结

Connecting Graph

给一个图中的n个节点, 记为 1 到 n . 在开始的时候图中没有边。
你需要完成下面两个方法:

  1. connect(a, b), 添加连接节点 ab 的边.
  2. query(a, b), 检验两个节点是否联通

思路:Union Find模板要会背诵;

public class ConnectingGraph {
    private class UnionFind {
        int[] father;
        int count;
        
        public UnionFind(int n) {
            father = new int[n+1];
            count = n;
            for(int i = 0; i <= n; i++) {
                father[i] = i;
            }
        }
        
        public int find(int x) {
            int j = x;
            while(father[j] != j) {
                j = father[j];
            }
            
            // path compression;
            while(x != j) {
                int fx = father[x];
                father[x] = j;
                x = fx;
            }
            
            return j;
        }
        
        public void union(int a, int b) {
            int root_a = find(a);
            int root_b = find(b);
            if(root_a != root_b) {
                father[root_a] = root_b;
                this.count--;
            }
        }
        
        public int getCount() {
            return this.count;
        }
    }
    
    UnionFind uf;
    public ConnectingGraph(int n) {
        uf = new UnionFind(n);
    }

    public void connect(int a, int b) {
        uf.union(a,b);
    }

    public boolean query(int a, int b) {
        return uf.find(a) == uf.find(b);
    }
}

Connecting Graph II

给一个图中的 n 个节点, 记为 1 到 n .在开始的时候图中没有边.
你需要完成下面两个方法:

  1. connect(a, b), 添加一条连接节点 a, b的边
  2. query(a), 返回图中含 a 的联通区域内节点个数

Example

例1:

输入:
ConnectingGraph2(5)
query(1)
connect(1, 2)
query(1)
connect(2, 4)
query(1)
connect(1, 4)
query(1)

输出:
[1,2,3,3]

思路:size的信息记录在老大哥那里,只需加一个size array就行,每次union的时候b的size += a.size;

public class ConnectingGraph2 {
    private class UnionFind {
        private int[] father;
        private int[] size;
        private int count;
        public UnionFind(int n) {
            this.father = new int[n+1];
            this.size = new int[n+1];
            for(int i = 0; i <= n; i++) {
                father[i] = i;
                size[i] = 1;
            }
            this.count = n;
        }
        
        public int find(int j) {
            int x = j;
            while(father[j] != j) {
                j = father[j];
            }
            
            // path compression;
            while(x != j) {
                int fx = father[x];
                father[x] = j;
                x = fx;
            }
            
            return j;
        }
        
        public void union(int a, int b) {
            int root_a = find(a);
            int root_b = find(b);
            if(root_a != root_b) {
                father[root_a] = root_b;
                size[root_b] += size[root_a];
            }
        }
        
        public int getSize(int a) {
            int root_a = find(a);
            return size[root_a];
        }
    }
    
    UnionFind uf;
    public ConnectingGraph2(int n) {
        uf = new UnionFind(n);
    }

    public void connect(int a, int b) {
        uf.union(a, b);
    }

    public int query(int a) {
        return uf.getSize(a);
    }
}

Connecting Graph III

给一个图中的 n 个节点, 记为 1 到 n . 在开始的时候图中没有边.
你需要完成下面两个方法:

  1. connect(a, b), 添加一条连接节点 a, b的边
  2. query(), 返回图中联通区域个数

Example

例1:

输入:
ConnectingGraph3(5)
query()
connect(1, 2)
query()
connect(2, 4)
query()
connect(1, 4)
query()

输出:[5,4,3,3]

思路:还是UnionFind的模板,注意count每次union的时候减减,就可以了。 

public class ConnectingGraph3 {
    private class UnionFind {
        private int[] father;
        private int count;
        
        public UnionFind(int n) {
            this.father = new int[n+1];
            for(int i = 0; i <= n; i++) {
                father[i] = i;
            }
            this.count = n;
        }
        
        public int find(int x) {
            int j = x;
            while(father[j] != j) {
                j = father[j];
            }
            
            // path compression;
            while(x != j) {
                int fx = father[x];
                father[x] = j;
                x = fx;
            }
            
            return j;
        }
        
        public void union(int a, int b) {
            int root_a = find(a);
            int root_b = find(b);
            if(root_a != root_b) {
                father[root_a] = root_b;
                count--;
            }
        }
        
        public int getCount() {
            return this.count;
        }
    }
    
    private UnionFind uf;
    public ConnectingGraph3(int n) {
        uf = new UnionFind(n);
    }
    
    public void connect(int a, int b) {
        uf.union(a, b);
    }

    public int query() {
        return uf.getCount();
    }
}

Number of Islands II

给定 n, m, 分别代表一个二维矩阵的行数和列数, 并给定一个大小为 k 的二元数组A. 初始二维矩阵全0. 二元数组A内的k个元素代表k次操作, 设第i个元素为 (A[i].x, A[i].y), 表示把二维矩阵中下标为A[i].x行A[i].y列的元素由海洋变为岛屿. 问在每次操作之后, 二维矩阵中岛屿的数量. 你需要返回一个大小为k的数组.

Example

样例 1:

输入: n = 4, m = 5, A = [[1,1],[0,1],[3,3],[3,4]]
输出: [1,1,2,2]
解释: 
0.  00000
    00000
    00000
    00000
1.  00000
    01000
    00000
    00000
2.  01000
    01000
    00000
    00000
3.  01000
    01000
    00000
    00010
4.  01000
    01000
    00000
    00011

样例 2:

输入: n = 3, m = 3, A = [[0,0],[0,1],[2,2],[2,1]]
输出: [1,1,2,2]

Notice

设定0表示海洋, 1代表岛屿, 并且上下左右相邻的1为同一个岛屿.

思路:这题就是加入一个点,然后跟他的四个连边进行union,如果四个边有1,那么进行union,result加入union之后的count;

这个题是二维变成一维的union Find。题目出的还是不错的,不亏是google的题目,很有筛选性;

/**
 * Definition for a point.
 * class Point {
 *     int x;
 *     int y;
 *     Point() { x = 0; y = 0; }
 *     Point(int a, int b) { x = a; y = b; }
 * }
 */

public class Solution {
    private class UnionFind {
        private int[] father;
        private int count;
        
        public UnionFind(int n) {
            father = new int[n+1];
            for(int i = 0; i <= n; i++) {
                father[i] = i;
            }
            count = n;
        }
        
        public int find(int x) {
            int j = x;
            while(father[j] != j) {
                j = father[j];
            }
            
            // path compression;
            while(x != j) {
                int fx = father[x];
                father[x] = j;
                x = fx;
            }
            
            return j;
        }
        
        public void union(int a, int b) {
            int root_a = find(a);
            int root_b = find(b);
            if(root_a != root_b) {
                father[root_a] = root_b;
                count--;
            }
        }
        
        public void setCount(int count) {
            this.count = count;
        }
        
        public int getCount() {
            return this.count;
        }
    }
    
    public List<Integer> numIslands2(int n, int m, Point[] operators) {
        List<Integer> result = new ArrayList<Integer>();
        if(n <= 0 || m <= 0 || operators == null || operators.length == 0) {
            return result;
        }
        
        // matrix记录状态;
        int[][] matrix = new int[n][m];
        
        //这里巧妙的将二维矩阵的union,转换为一维的union;
        UnionFind uf = new UnionFind(n*m);
        
        int[] dx = {0,0,1,-1};
        int[] dy = {1,-1,0,0};
        
        int count = 0;
        for(Point point: operators) {
            int x = point.x;
            int y = point.y;
            if(matrix[x][y] == 1) {
                result.add(count);
                continue;
            }
            
            matrix[x][y] = 1;
            count++;
            // 先设置好初始值count,假设没有连边;
            uf.setCount(count);
            
            for(int k = 0; k < 4; k++){
                int nx = x + dx[k];
                int ny = y + dy[k];
                //如果四个边上的点也是1,那么就union;
                if(0 <= nx && nx < n && 0 <= ny && ny < m && matrix[nx][ny] == 1){
                    uf.union(m*x + y , m*nx + ny);
                }
            }
            // union完之后,count取出来,方便下一次计算,同时加入result;
            count = uf.getCount();
            result.add(count);
        }
        return result;
    }
}

Minimum Spanning Tree

给出一些Connections,即Connections类,找到一些能够将所有城市都连接起来并且花费最小的边。
如果说可以将所有城市都连接起来,则返回这个连接方法;不然的话返回一个空列表。

Example

样例 1:

输入:
["Acity","Bcity",1]
["Acity","Ccity",2]
["Bcity","Ccity",3]
输出:
["Acity","Bcity",1]
["Acity","Ccity",2]

样例 2:

输入:
["Acity","Bcity",2]
["Bcity","Dcity",5]
["Acity","Dcity",4]
["Ccity","Ecity",1]
输出:
[]

解释:
没有办法连通

Notice

返回按cost排序的连接方法,如果cost相同就按照city1进行排序,如果city1也相同那么就按照city2进行排序。

思路:需要判断是否连通满了,所以需要用unionfind来判断;如何利用最小的边去connect,核心思想就是:首先把边按照value sort一下,然后依次取出来,给city的点进行编号,然后看两个点是否connect,不connect则conenct,这样每次用的边都是value最小的,那么最后connect完了之后,所用到的value就是最小的,而且由于点全部connect了,那么后面value比较大的边,就不会用来connect了;最后如果能够形成的边如果是n-1那么就是正确答案,否则不能connect所有的点,return empty list;

/**
 * Definition for a Connection.
 * public class Connection {
 *   public String city1, city2;
 *   public int cost;
 *   public Connection(String city1, String city2, int cost) {
 *       this.city1 = city1;
 *       this.city2 = city2;
 *       this.cost = cost;
 *   }
 * }
 */
public class Solution {
    
    private class UnionFind {
        private int[] father;
        private int count;
        
        public UnionFind(int n) {
            father = new int[n+1];
            for(int i = 0; i <= n; i++) {
                father[i] = i;
            }
            count = n;
        }
        
        public int find(int x) {
            int j = x;
            while(father[j] != j) {
                j = father[j];
            }
            
            // path compression;
            while(x != j) {
                int fx = father[x];
                father[x] = j;
                x = fx;
            }
            
            return j;
        }
        
        public void union(int a, int b) {
            int root_a = find(a);
            int root_b = find(b);
            if(root_a != root_b) {
                father[root_a] = root_b;
                count--;
            }
        }
        
        public int getCount() {
            return this.count;
        }
    }
    
    private class ConnectionComparator implements Comparator<Connection> {
        @Override
        public int compare(Connection a, Connection b) {
            if(a.cost != b.cost) {
                return a.cost - b.cost;
            } else {
                if(a.city1.equals(b.city1)){
                    return a.city2.compareTo(b.city2);
                } else {
                    return a.city1.compareTo(b.city1);
                }
            }
        }
    }
    
    public List<Connection> lowestCost(List<Connection> connections) {
        // 需要判断是否连通满了,所以需要用unionfind来判断;
        // 如何利用最小的边去connect,核心思想就是:首先把边按照value sort一下,然后依次取出来,给city的点进行编号,然后看两个点是否connect,不connect则conenct,这样每次用的边都是value最小的,那么最后connect完了之后,所用到的value就是最小的,而且由于点全部connect了,那么后面value比较大的边,就不会用来connect了;
        // 最后如果能够形成的边如果是n-1那么就是正确答案,否则不能connect所有的点,return empty
        List<Connection> result = new ArrayList<Connection>();
        Collections.sort(connections, new ConnectionComparator());
        int n = 0;
        // 求出所有的点,并对其进行编号;
        HashMap<String, Integer> hashmap = new HashMap<String, Integer>();
        for(Connection connection: connections) {
            if(!hashmap.containsKey(connection.city1)){
                hashmap.put(connection.city1, ++n);
            }
            if(!hashmap.containsKey(connection.city2)){
                hashmap.put(connection.city2, ++n);
            }
        }
        
        UnionFind uf = new UnionFind(n);
        // 依次取出最小cost的边,如果没有相连,连起来加入result;
        for(Connection connection: connections) {
            int a = hashmap.get(connection.city1);
            int b = hashmap.get(connection.city2);
            if(uf.find(a) != uf.find(b)){
                uf.union(a, b);
                result.add(connection);
            }
        }
        
        if(result.size() == n -1) {
            return result;
        } else {
            return new ArrayList<Connection>();
        }
    }
}

Accounts Merge

给定一个帐户列表,每个元素accounts [i]是一个字符串列表,其中第一个元素accounts [i] [0]是账户名称,其余元素是这个帐户的电子邮件。
现在,我们想合并这些帐户。
如果两个帐户有相同的电子邮件地址,则这两个帐户肯定属于同一个人。
请注意,即使两个帐户具有相同的名称,它们也可能属于不同的人,因为两个不同的人可能会使用相同的名称。
一个人可以拥有任意数量的账户,但他的所有帐户肯定具有相同的名称。
合并帐户后,按以下格式返回帐户:每个帐户的第一个元素是名称,其余元素是按字典序排序后的电子邮件。
帐户本身可以按任何顺序返回。

Example

样例 1:
	输入:
	[
		["John", "[email protected]", "[email protected]"],
		["John", "[email protected]"],
		["John", "[email protected]", "[email protected]"],
		["Mary", "[email protected]"]
	]
	
	输出: 
	[
		["John", '[email protected]', '[email protected]', '[email protected]'],
		["John", "[email protected]"],
		["Mary", "[email protected]"]
	]

	解释: 
	第一个第三个John是同一个人的账户,因为这两个账户有相同的邮箱:"[email protected]".
	剩下的两个账户分别是不同的人。因为他们没有和别的账户有相同的邮箱。

	你可以以任意顺序返回结果。比如:
	
	[
		['Mary', '[email protected]'],
		['John', '[email protected]'],
		['John', '[email protected]', '[email protected]', '[email protected]']
	]
	也是可以的。

思路:经典的union find题目。以email为点来进行union,email刚开始的默认index是row index,此时union find的是accounts的index,0,1,2,如果后面email有相同的,则跟row index union。然后再根据index相同来收集emails。最后加入email去找index,找到name,然后combine成result,学会用putIfAbsent.
 

public class Solution {
    /**
     * @param accounts: List[List[str]]
     * @return: return a List[List[str]]
     */
    private class UnionFind {
        private int[] father;
        private int count;
        
        public UnionFind(int n) {
            father = new int[n+1];
            for(int i = 0; i <= n; i++) {
                father[i] = i;
            }
            count = n;
        }
        
        public int find(int x) {
            int j = x;
            while(father[j] != j) {
                j = father[j];
            }
            
            // path compression;
            while(x != j) {
                int fx = father[x];
                father[x] = j;
                x = fx;
            }
            
            return j;
        }
        
        public void union(int a, int b) {
            int root_a = find(a);
            int root_b = find(b);
            if(root_a != root_b) {
                father[root_a] = root_b;
                count--;
            }
        }
        
        public int getCount() {
            return this.count;
        }
    }
     
    public List<List<String>> accountsMerge(List<List<String>> accounts) {
        List<List<String>> results = new ArrayList<List<String>>();
        if(accounts == null || accounts.size() == 0) {
            return results;
        }
        HashMap<String, Integer> emailToIndex = new HashMap<String, Integer>();
        UnionFind uf = new UnionFind(accounts.size());
        for(int i = 0; i < accounts.size(); i++) {
            List<String> list = accounts.get(i);
            for(int j = 1; j < list.size(); j++) {
                String email = list.get(j);
                emailToIndex.putIfAbsent(email, i);
                uf.union(emailToIndex.get(email), i);
            }
        }
        
        // collect result;
        // 首先按照root index,去搜集这个union集合下面的email
        HashMap<Integer, List<String>> indexToEmailList = new HashMap<Integer, List<String>>();
        for(String email: emailToIndex.keySet()){
            int index = emailToIndex.get(email);
            int root = uf.find(index);
            indexToEmailList.putIfAbsent(root, new ArrayList<String>());
            indexToEmailList.get(root).add(email);
        }
        
        // 然后按照email的index,去找name,然后整合输出result;
        for(Integer index: indexToEmailList.keySet()){
            String name = accounts.get(index).get(0);
            List<String> list = indexToEmailList.get(index);
            Collections.sort(list);
            list.add(0, name);
            results.add(list);
        }
        return results;
    }
}

Graph Valid Tree

给出 n 个节点,标号分别从 0 到 n - 1 并且给出一个 无向 边的列表 (给出每条边的两个顶点), 写一个函数去判断这张`无向`图是否是一棵树

Example

样例 1:

输入: n = 5 edges = [[0, 1], [0, 2], [0, 3], [1, 4]]
输出: true.

思路:tree的条件就是: 

1.只有一个连通块, connected component = 1;

2. 边的数目 = 点的数目-1; edges = points - 1;

public class Solution {
    /**
     * @param n: An integer
     * @param edges: a list of undirected edges
     * @return: true if it's a valid tree, or false
     */
    private class UnionFind {
        private int[] father;
        private int count;
        public UnionFind(int n) {
            this.father = new int[n+1];
            // 千万别忘记初始化father数组;
            for(int i = 0; i <= n; i++) {
                father[i] = i;
            }
            this.count = n;
        }
        
        public int find(int x) {
            int j = x;
            while(father[j] != j) {
                j = father[j];
            }
            
            // path compression;
            while(x != j) {
                int fx = father[x];
                father[x] = j;
                x = fx;
            }
            
            return j;
        }
        
        public void union(int a, int b) {
            int root_a = find(a);
            int root_b = find(b);
            if(root_a != root_b) {
                father[root_a] = root_b;
                count--;
            }
        }
        
        public int getCount() {
            return count;
        }
    }
    
    public boolean validTree(int n, int[][] edges) {
        if(n <= 0 || edges == null){
            return false;
        }
        
        if(edges.length != n - 1) {
            return false;
        }
        
        UnionFind uf = new UnionFind(n);
        for(int i = 0; i < edges.length; i++) {
            uf.union(edges[i][0], edges[i][1]);
        }
        
        return uf.getCount() == 1;
    }
}

Maximum Association Set

亚麻卖书,每本书都有与其关联性很强的书,给出ListA与ListB,表示ListA[i]ListB[i]有关联,输出互相关联的最大集合。(输出任意顺序),题目保证只有一个最大的集合。

Example

样例 1:
	输入:  ListA = ["abc","abc","abc"], ListB = ["bcd","acd","def"]
	输出:  ["abc","acd","bcd","def"]
	解释:
	"abc" 和其他书均有关联,全集就是最大集合。
	
样例 2:
	输入:  ListA = ["a","b","d","e","f"], ListB = ["b","c","e","g","g"]
	输出:  ["d","e","f","g"]
	解释:
	关联的集合有 [a, b, c] 和 [d, e, g, f], 最大的是 [d, e, g, f]

Notice

  • 书籍的数量不会超过5000

思路:以书为node,建立一个<string,Integer> mapping, 注意size是2*n, 用不同的index代表不同的书,如果hashmap里面有了,就不用加了,是同一本书;union完之后,扫描一遍求出最大的size和最大的index,然后根据index来收集书名;注意去重,因为我是扫描了一遍书名,书名就有重复的,必须去重复;

public class Solution {
    /**
     * @param ListA: The relation between ListB's books
     * @param ListB: The relation between ListA's books
     * @return: The answer
     */
    private class UnionFind {
        private int[] father;
        private int[] size;
        
        public UnionFind(int n) {
            father = new int[n+1];
            size = new int[n+1];
            for(int i = 0; i <= n; i++) {
                father[i] = i;
                size[i] = 1;
            }
        }
        
        public int find(int x) {
            int j = x;
            while(father[j] != j) {
                j = father[j];
            }
            
            // path compression;
            while(x != j) {
                int fx = father[x];
                father[x] = j;
                x = fx;
            }
            
            return j;
        }
        
        public void union(int a, int b) {
            int root_a = find(a);
            int root_b = find(b);
            if(root_a != root_b) {
                father[root_a] = root_b;
                size[root_b] += size[root_a];
            }
        }
        
        public int getSize(int index){
            return this.size[index];
        }
    }
    
    public List<String> maximumAssociationSet(String[] ListA, String[] ListB) {
        List<String> result = new ArrayList<String>();
        if(ListA == null || ListA.length == 0 || ListB == null || ListB.length == 0) {
            return result;
        }
        
        int n = ListA.length;
        // 注意这是两倍的size;
        UnionFind uf = new UnionFind(2*n); 
        // 只用一个map就可以建立mapping;
        HashMap<String, Integer> map = new HashMap<String, Integer>();

        for(int i = 0; i < ListA.length; i++) {
            if(!map.containsKey(ListA[i])){
                map.put(ListA[i], i);
            }
            if(!map.containsKey(ListB[i])){
                map.put(ListB[i], n+i);
            }
            uf.union(map.get(ListA[i]), map.get(ListB[i]));
        }
        
        int globalmax = 0;
        int maxindex = 0;
        for(int i = 0; i < ListA.length; i++){
            int index = uf.find(map.get(ListA[i]));
            int size = uf.getSize(index);
            if(size > globalmax) {
                globalmax = size;
                maxindex = index;
            }
        }
        
        HashSet<String> set = new HashSet<String>();
        for(int i = 0; i < ListA.length; i++) {
            int root_a = uf.find(map.get(ListA[i]));
            int root_b = uf.find(map.get(ListB[i]));
            if(root_a == maxindex && !set.contains(ListA[i])){
                result.add(ListA[i]);
                set.add(ListA[i]);
            }
            if(root_b == maxindex && !set.contains(ListB[i])){
                result.add(ListB[i]);
                set.add(ListB[i]);
            }
        }
        return result;
    }
}
发布了562 篇原创文章 · 获赞 13 · 访问量 17万+

猜你喜欢

转载自blog.csdn.net/u013325815/article/details/103905032