Union Find (Disjoint Set) - How to Succeed in Algorithms Interview


Union Find - How to Succeed in Algorithms Interview

How to Succeed in Algorithms Interview Series


Applications of Union Find(Disjoint Set)

  • Connected components/groups, connections
  • Find cycle
  • useful when give edges of graph

Time complexity

  • without anything: O(N)
  • with Union by Rank: O(logN)
  • with Path Compression: O(α(N)) which is inverse Ackermann function.

Info we can get

  • find root, union
  • count of components, size of each component
  • whether two nodes are connected or in same group

Implementation Detail

  • UnionFind(int size)
  • int[] parent; int[] rank;
  • Map<String, String> parents or parent[];
  • find, union, isConnected, findCount
  • highestRank, maxUnion
  • find grouped ids

Summary

  • dummy root node

Dummy node/parent

Given a 2D board containing 'X' and 'O', capture all regions surrounded by 'X'. A region is captured by flipping all 'O's into 'X's in that surrounded region.
For example,
X X X X  
X O O X  
X X O X  
X O X X  
After running your function, the board should be:
X X X X  
X X X X  
X X X X  
X O X X
  • we connect all the ‘O’ nodes on the boundary to a dummy node, and then connect each ‘O’ node to its neighbour ‘O’ nodes
int row, col;
public void solve(char[][] board) {
    if (board == null || board.length == 0) return;
    row = board.length;
    col = board[0].length;
    UnionFind uf = new UnionFind(row*col+1);
    int dummy = row*col;
    for (int i = 0; i < row; i++) {
        for (int j = 0; j < col; j++) {
            if (board[i][j] == 'O') {
                if (i == 0 || i == row-1 || j == 0 || j == col-1) uf.union(node(i, j), dummy);
                else {
                    if (i > 0 && board[i-1][j] == 'O') uf.union(node(i, j), node(i-1, j));
                    if (i > 0 && board[i+1][j] == 'O') uf.union(node(i, j), node(i+1, j));
                    if (j > 0 && board[i][j-1] == 'O') uf.union(node(i, j), node(i, j-1));
                    if (j > 0 && board[i][j+1] == 'O') uf.union(node(i, j), node(i, j+1));
                }
            }
        }
    }
    for (int i = 0; i < row; i++) {
        for (int j = 0; j < col; j++) {
            if (uf.isConnected(node(i, j), dummy)) board[i][j] = 'O';
            else board[i][j] = 'X';
        }
    }
}
public int node(int i, int j) {
    return i*col+j;
}

What to Group/Union

Map<Integer, Integer> map = new HashMap<>(); // parent
Map<Integer, Integer> c = new HashMap<>(); // count
int res=1;
public int longestConsecutive(int[] a) {
    if(a.length==0) return 0;
    for(int i=0;i<a.length;i++){
        int cur = a[i];
        if(map.containsKey(cur)) continue;
        map.put(cur,cur);
        c.put(cur, 1);
        if(map.containsKey(cur-1)) union(cur-1, cur);
        if(map.containsKey(cur+1)) union(cur+1, cur);
    }
    return res;
}
int find(int x){
    if(map.get(x)==x) return x;
    int par = find(map.get(x));
    map.put(x, par); // path compression
    return par;
}
void union(int x, int y){
    int px = find(x);
    int py = find(y);
    int newCount = c.get(py)+c.get(px);
    if(c.get(px)<c.get(py)){
        map.put(px, py);    
        c.put(py, newCount);
    }else{
        map.put(py, px);    
        c.put(px, newCount);
    }
    res=Math.max(res, newCount);
}
public int largestComponentSize(int[] A) {
    int N = A.length;
    Map<Integer, Integer> map = new HashMap<>();// key is the factor, val is the node index
    UF uf = new UF(N);
    for (int i = 0; i < N; i++){
        int a = A[i];
        for (int j = 2; j * j <= a; j++){
            if (a % j == 0){
                if (!map.containsKey(j)){//this means that no index has claimed the factor yet
                    map.put(j, i);
                }else{//this means that one index already claimed, so union that one with current
                    uf.union(i, map.get(j));
                }
                if (!map.containsKey(a/j)){
                    map.put(a/j, i);
                }else{
                    uf.union(i, map.get(a/j));
                }
            }
        }
        if (!map.containsKey(a)){//a could be factor too. Don't miss this
            map.put(a, i);
        }else{
            uf.union(i, map.get(a));
        }
    }
    return uf.max;
}

Union-Find - Weight

  • Parent node contains extra info
  • HARD LeetCode 399 - Evaluate Division
    class Node {
    public String parent;
    public double ratio;
    }
    class UnionFindSet {
    private Map<String, Node> parents = new HashMap<>();
    public Node find(String s) {
      if (!parents.containsKey(s)) return null;
      Node n = parents.get(s);
      if (!n.parent.equals(s)) {
        Node p = find(n.parent);
        n.parent = p.parent;
        n.ratio *= p.ratio;
      }
      return n;
    }
    public void union(String s, String p, double ratio) {
      boolean hasS = parents.containsKey(s);
      boolean hasP = parents.containsKey(p);
      if (!hasS && !hasP) {
        parents.put(s, new Node(p, ratio));
        parents.put(p, new Node(p, 1.0));
      } else if (!hasP) {
        parents.put(p, new Node(s, 1.0 / ratio));
      } else if (!hasS) {
        parents.put(s, new Node(p, ratio));
      } else {
        Node rS = find(s);
        Node rP = find(p);
        rS.parent = rP.parent;
        rS.ratio = ratio / rS.ratio * rP.ratio;
      }
    }
    }
    public double[] calcEquation(String[][] equations, double[] values, String[][] queries) {
    UnionFindSet u = new UnionFindSet();
    for (int i = 0; i < equations.length; ++i)
      u.union(equations[i][0], equations[i][1], values[i]);
    double[] ans = new double[queries.length];
    for (int i = 0; i < queries.length; ++i) {      
      Node rx = u.find(queries[i][0]);
      Node ry = u.find(queries[i][1]);
      if (rx == null || ry == null || !rx.parent.equals(ry.parent))
        ans[i] = -1.0;        
      else
        ans[i] = rx.ratio / ry.ratio;
    }
    return ans;
    }

Info we can get

  • group count, max group,
// https://leetcode.com/articles/minimize-malware-spread/
for (int node: initial)
    count[dsu.find(node)]++;

Time complexity

  • without anything: O(N)
  • with Union by Rank: O(logN)
  • with union by rank + Path Compression: O(α(N)) which is inverse Ackermann function.
    • This function has a value {(n)<5} for any value of n that can be written in this physical universe.

Classic Problems

int x_set = find(parent, edge.source);
int y_set = find(parent, edge.destination);
//check if source vertex and destination vertex belongs to the same set
// if in same set then cycle has been detected else combine them into one set
if(x_set==y_set)
    return true;
else
    union(parent, x_set, y_set);
int res = Integer.MAX_VALUE;
public int minTransfers(int[][] transactions) {
   HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
   for (int[] transaction : transactions) {
       map.put(transaction[0], map.getOrDefault(transaction[0], 0)-transaction[2]);
       map.put(transaction[1], map.getOrDefault(transaction[1], 0)+transaction[2]);
   }
   ArrayList<Integer> depts = new ArrayList<Integer>();
    for (int dept : map.values()) {
        if (dept != 0) depts.add(dept);
    }
    helper(depts, 0, 0);
    return res;
}

public void helper(ArrayList<Integer> depts, int start, int count) {
  while (start<depts.size() && depts.get(start)==0) start++;
  if (start == depts.size()) {
      res = Math.min(res, count);
      return;
  }
  for (int i=start+1; i<depts.size(); i++) {
      if (depts.get(start)<0&&depts.get(i)>0 || depts.get(start)>0&&depts.get(i)<0) {
          depts.set(i, depts.get(i)+depts.get(start));
          helper(depts, start+1, count+1);
          depts.set(i, depts.get(i)-depts.get(start));
      }
  }
}

Examples

Design a data structure to support following functions:
1. setManager(A, B) sets A as a direct manager of B
2. setPeer(A, B) sets A as a colleague of B. After that, A and B will have the same direct Manager.
3. query(A, B) returns if A is in the management chain of B.
Every person only has 1 direct manager.

Union Find Implementation

  • rank is only used in union
Using Array
class UnionFind {
    int[] parent;
    int[] rank;
    int count;
    public UnionFind(int n) {
        parent = new int[n];
        rank = new int[n];
        count = n;
        for (int i = 0; i < n; ++i) {
            parent[i] = i;
            rank[i] = 0;
        }
    }
    public int find(int x) { // path compression
        // only the rank of the root matters, used in union op.
        if (parent[x] != x)
            parent[x] = find(parent[x]);
        return parent[x];
    }
    public void union(int x, int y) { // union with rank
        int rootx = find(x);
        int rooty = find(y);
        if (rootx != rooty) {
            if (rank[rootx] > rank[rooty]) {
                parent[rooty] = rootx;
            } else {
                parent[rootx] = rooty;
                if (rank[rootx] == rank[rooty])
                    rank[rooty] += 1;
            }
            count--;
        }
    }
    public int getCount() {
        return count;
    }
}
Using HashMap
class UnionFind{
    //HashMap maintaining key - > value (child -> parent) relationship
    HashMap parent;
    public UnionFind(HashSet hs){
        parent = new HashMap();
        for(int i : hs){
            parent.put(i, i);
        }
    }
    public int root(int i){
        while(i != parent.get(i)){
            parent.put(i, parent.get(parent.get(i)));
            i = parent.get(i);
        }
        return i;
    }
    public void union(int i, int j){
        int p = root(i);
        int q = root(j);
        if(p != q){
            parent.put(p, q);
        }
    }
}

Labels

adsense (5) Algorithm (69) Algorithm Series (35) Android (7) ANT (6) bat (8) Big Data (7) Blogger (14) Bugs (6) Cache (5) Chrome (19) Code Example (29) Code Quality (7) Coding Skills (5) Database (7) Debug (16) Design (5) Dev Tips (63) Eclipse (32) Git (5) Google (33) Guava (7) How to (9) Http Client (8) IDE (7) Interview (88) J2EE (13) J2SE (49) Java (186) JavaScript (27) JSON (7) Learning code (9) Lesson Learned (6) Linux (26) Lucene-Solr (112) Mac (10) Maven (8) Network (9) Nutch2 (18) Performance (9) PowerShell (11) Problem Solving (11) Programmer Skills (6) regex (5) Scala (6) Security (9) Soft Skills (38) Spring (22) System Design (11) Testing (7) Text Mining (14) Tips (17) Tools (24) Troubleshooting (29) UIMA (9) Web Development (19) Windows (21) xml (5)