Decision Guide: Which Structure to Use?
Quick Selection
| Problem Pattern | Use | Why |
|---|---|---|
| Are two nodes connected? Merge groups? | Union-Find | \(O(\alpha(n))\) per op — near-constant |
| Autocomplete / prefix matching / word existence | Trie | O(L) per op (L = word length) regardless of vocabulary size |
| Prefix sums with point updates | Fenwick Tree (BIT) | \(O(\log n)\) update + query; simpler than segment tree |
| Range queries with range updates (min, max, sum, GCD...) | Segment Tree | \(O(\log n)\) any range query; fully general |
| Static range min/max (no updates) | Sparse Table | \(O(1)\) query after \(O(n \log n)\) preprocessing — but immutable |
flowchart TD
Q1{Connectivity or group membership?}
Q1 -->|Yes| UF[Union-Find]
Q1 -->|No| Q2{Strings or prefix queries?}
Q2 -->|Yes| TR[Trie]
Q2 -->|No| Q3{Range queries on array?}
Q3 -->|No| Q4[Array or HashMap is sufficient]
Q3 -->|Yes| Q5{Point updates and range sum only?}
Q5 -->|Yes| FW[Fenwick Tree - BIT]
Q5 -->|No| Q6{Range updates or min/max/GCD?}
Q6 -->|Yes| ST[Segment Tree]
Q6 -->|No| SP[Sparse Table - static O-1 query]
Union-Find (Disjoint Set Union)
Mental Model
Problem it solves: Efficiently answer "are A and B in the same group?" and "merge the groups containing A and B". Canonical for connectivity problems in undirected graphs.
When NOT to use: Directed graphs (connectivity is not symmetric), need to query which group a node was in at a past time (use persistent DSU), need to actually traverse the group members (use adjacency list + BFS).
Common misconception: "Union-Find finds shortest paths." It does NOT — it only answers yes/no connectivity. For paths, use BFS/DFS or Dijkstra.
Union-Find Implementation
Union-Find Applications
Classic Use Cases
- Kruskal's MST algorithm — add edge if endpoints are not yet connected; uses Union-Find to detect cycles in \(O(\alpha(n))\)
- Dynamic connectivity — network routing, social network friend groups
- Percolation — does a grid percolate (top-to-bottom connected path of open sites)?
- Redundant Connection (LeetCode 684), Accounts Merge (LeetCode 721), Satisfiability of Equality Equations (LeetCode 990)
Trie (Prefix Tree)
Mental Model
Problem it solves: Store and search a set of strings where prefix queries are needed. O(L) per operation (L = word length) regardless of how many words are stored.
When NOT to use: Memory is constrained and the alphabet is large (each node can have 26+ children). Use a hash-based variant (TrieMap with dict children) for large alphabets. If you only need exact lookups, a hash set is simpler.
Common misconception: "Trie is always better than a hash set for strings." Hash set O(L) lookup is often faster in practice due to cache effects. Trie wins when prefix queries are needed.
Trie Implementation
Trie Applications
Classic Use Cases
- Autocomplete — IDE suggestions, search bar prefixes
- Spell checker — find closest word in trie using DFS with edit distance
- IP routing — longest prefix match for CIDR blocks (binary trie on IP bits)
- Word Search II (LeetCode 212) — build trie from word list, DFS the grid; avoids re-checking each word individually
- Maximum XOR Pair (LeetCode 421) — binary trie, greedily pick opposite bit
Fenwick Tree (Binary Indexed Tree)
Mental Model
Problem it solves: Maintain a mutable array where you frequently need prefix sums. \(O(\log n)\) update and \(O(\log n)\) prefix query — much faster than recomputing from scratch (\(O(n)\)).
When NOT to use: Range minimum/maximum queries (BIT only supports invertible operations like sum). Need range updates AND range queries (use Segment Tree with lazy propagation).
Common misconception: BIT is 1-indexed. Index 0 is unused. Every tutorial that forgets this causes subtle off-by-one bugs. Always allocate n+1 elements.
Fenwick Tree Implementation
Segment Tree
Mental Model
Problem it solves: Any range query (sum, min, max, GCD, count...) with point or range updates. The most general range data structure — if BIT doesn't support your operation, Segment Tree does.
When NOT to use: Only prefix sums needed (BIT is simpler and faster in practice). Static array, no updates (Sparse Table gives \(O(1)\) range min/max). Memory is extremely tight (Segment Tree uses 4n space).
Common misconception: Segment Trees are hard to implement. They follow a single recursive pattern — build from leaves, query/update by splitting at midpoint. Once you internalize the pattern, all variants follow.
Segment Tree Implementation
Comprehensive Comparison
| Structure | Build | Update | Query | Space | Supports |
|---|---|---|---|---|---|
| Union-Find | \(O(n)\) | \(O(\alpha(n))\) | \(O(\alpha(n))\) | \(O(n)\) | Connectivity, group merge |
| Trie | \(O(n \times L)\) | \(O(L)\) | \(O(L)\) | \(O(n \times L)\) | Prefix search, word existence |
| Fenwick Tree | \(O(n \log n)\) | \(O(\log n)\) | \(O(\log n)\) | \(O(n)\) | Prefix sums (invertible ops) |
| Segment Tree | \(O(n)\) | \(O(\log n)\) | \(O(\log n)\) | \(O(4n)\) | Any range query + updates |
| Sparse Table | \(O(n \log n)\) | N/A | \(O(1)\) | \(O(n \log n)\) | Static range min/max only |
Interview & Practice
Quick Check
- Explain why path compression makes Union-Find \(O(\alpha(n))\) — what does the tree look like after repeated finds?
- Implement a Trie that supports wildcard search (e.g.,
search("a.c")matches "abc", "aXc"). - Given array [2,4,5,3,6,1], use Fenwick Tree to answer: how many elements to the right of index i are smaller than arr[i]? (LeetCode 315)
Key LeetCode Problems
- Union-Find: 684, 721, 990, 547 (Number of Provinces), 200 (Number of Islands)
- Trie: 208 (Implement Trie), 212 (Word Search II), 421 (Max XOR)
- Fenwick Tree: 307 (Range Sum Query Mutable), 315 (Count Smaller), 493 (Reverse Pairs)
- Segment Tree: 307, 715 (Range Module), 732 (My Calendar III)