Implement Union-Find and track components
Company: Motive
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Onsite
Quick Answer: This question evaluates understanding and implementation of the Disjoint Set Union (Union-Find) data structure, covering union by rank, path compression, API design for union(a, b), connected(a, b), count(), and analysis of time and space complexity.
Constraints
- 1 <= n <= 10^5
- 0 <= a, b < n
- 0 <= number of operations <= 10^5
- Each operation type is either "union" or "connected"
- Self-operations are allowed (e.g. connected(x, x) is always true; union(x, x) is a no-op that leaves the count unchanged)
Examples
Input: (5, [["union", 0, 1], ["union", 2, 3], ["connected", 0, 1], ["connected", 0, 2], ["union", 1, 3], ["connected", 0, 3], ["connected", 0, 4]])
Expected Output: [4, 3, true, false, 2, true, false]
Explanation: Union 0-1 -> 4 comps; union 2-3 -> 3 comps; 0~1 true; 0~2 false; union 1-3 merges {0,1} and {2,3} -> 2 comps; 0~3 now true; node 4 isolated so 0~4 false.
Input: (1, [["connected", 0, 0]])
Expected Output: [true]
Explanation: A single node is always connected to itself.
Input: (4, [["union", 0, 1], ["union", 1, 2], ["union", 2, 3], ["union", 0, 3]])
Expected Output: [3, 2, 1, 1]
Explanation: Chain unions reduce 4 -> 3 -> 2 -> 1 components. The final union(0,3) is redundant (already in the same set) so the count stays at 1.
Input: (3, [["connected", 0, 1], ["connected", 1, 2]])
Expected Output: [false, false]
Explanation: No unions performed; every node is in its own component, so all connectivity queries are false.
Input: (6, [["union", 0, 1], ["union", 2, 3], ["union", 4, 5], ["union", 1, 2], ["connected", 0, 3], ["connected", 0, 5]])
Expected Output: [5, 4, 3, 2, true, false]
Explanation: Three pairwise unions give 5->4->3 comps; union 1-2 merges {0,1} with {2,3} -> 2 comps; 0~3 true (same merged set); 0~5 false ({4,5} is separate).
Hints
- Maintain a parent array where parent[i] points toward the representative (root) of i's set. Initialize parent[i] = i so every node starts in its own component.
- find(x) walks to the root. Apply path compression by repointing every node on the path directly to the root, which flattens the tree for future queries.
- Use union by rank: attach the shorter tree under the taller one, and only increment rank when two equal-rank roots merge. Decrement the component count only when the two roots actually differ.
- connected(a, b) is simply find(a) == find(b). For thread-safety in a concurrent setting you'd guard find/union with a lock or use a lock-free CAS-based union, since path compression mutates shared state.