PATTERN 2 — ORDERING, PARTITIONS & SPANNING STRUCTURE#
The machine: impose a linear order on a DAG, or maintain a partition under merges. Both are about structure over the whole graph rather than a walk through it.
2.1 Sub-variant map#
| Sub-variant | The one idea | |
|---|---|---|
| A | Directed cycle detection — three colours | visited is not enough; "in progress" and "finished" are different facts |
| B | Kahn's algorithm | Indegree-zero queue; a short output is the cycle report |
| C | Topological sort as a modelling exercise | The edges are not given — deriving them is the problem |
| D | DP over a DAG | Acyclic ⇒ memoise, no visited |
| E | Union-Find — the structure itself | Union by size + path compression, or it is not O(α) |
| F | Union-Find for counting and grouping | The answer is usually n − components or edges − (n − components) |
| G | Union-Find over time / with extra state | Merges arrive in an order you choose or are given |
| H | Minimum spanning tree | Cheapest connection, not shortest path |
| I | Bridges & articulation points | One DFS with low-links replaces E connectivity checks |
| J | Functional graphs (out-degree exactly 1) | Every component is a rho; label by visit time |
| K | Eulerian path | Every edge once. Append on the way out, then reverse |
2.2 Problems#
ADirected cycle detection, three colours#
White = untouched, grey = on the current stack, black = fully explored. Grey-to-grey is a cycle; grey-to-black is not.
| Solved | # | Problem | Diff | Sub-variant | Why it's essential |
|---|---|---|---|---|---|
| 45★ | 207. Course Schedule | Medium | A | The atom. Both the 3-colour DFS and the Kahn answer; write both | |
| 46★⚠︎ | 802. Find Eventual Safe States | Medium | A | A single visited set conflates "currently on the stack" with "proved safe", and reports unsafe nodes as safe. 3-colour, or Kahn on the reversed graph |
BKahn's algorithm#
Indegree-zero queue. If the emitted order is shorter than n, the remainder is a cycle — you get detection for free.
| Solved | # | Problem | Diff | Sub-variant | Why it's essential |
|---|---|---|---|---|---|
| 47★ | 210. Course Schedule II | Medium | B | The template. Decrement on pop, enqueue at zero, compare order.size() to n | |
| 48★ | 2050. Parallel Courses III | Hard | B | Critical path: finish[v] = max(finish[u]) + time[v] accumulated along the topological order | |
| 49○ | 1136. Parallel Courses PRO | Medium | B | The level count is the answer. Free substitute: 2050 with all durations set to 1 |
CTopological sort as a modelling exercise#
The traversal is 15 lines. Deriving the edge set from prose is the entire difficulty.
| Solved | # | Problem | Diff | Sub-variant | Why it's essential |
|---|---|---|---|---|---|
| 50★ | 2115. Find All Possible Recipes from Given Supplies | Medium | C | Nodes of two kinds (recipes and supplies) in one graph; indegree counted only over missing ingredients | |
| 51★⚠︎ | 269. Alien Dictionary PRO | Hard | C | Only the first differing character of adjacent words is an edge — every other char pair is noise. And ["abc","ab"] is invalid input, not an empty edge set. Free substitute: 2392 for the same "derive edges from constraints" step | |
| 52★ | 310. Minimum Height Trees | Medium | C | Topological peeling on an undirected tree: repeatedly strip degree-1 nodes. The answer is the last 1 or 2 standing. Not a real topological sort — know why it still terminates | |
| 53○ | 444. Sequence Reconstruction PRO | Medium | C | The order is unique iff the queue never holds more than one node | |
| 54○ | 851. Loud and Rich | Medium | C | Memoised DFS over the "richer than" DAG — the bridge to 2.D | |
| 55○ | 1203. Sort Items by Groups Respecting Dependencies | Hard | C | Two nested topological sorts, groups then items. Ungrouped items each need their own synthetic group | |
| 56○ | 2392. Build a Matrix With Conditions | Hard | C | Two independent topological sorts producing two coordinate axes |
DDP over a DAG (memoised DFS)#
If the graph is acyclic, memoisation is legal and a visited set is a bug.
| Solved | # | Problem | Diff | Sub-variant | Why it's essential |
|---|---|---|---|---|---|
| 57★⚠︎ | 329. Longest Increasing Path in a Matrix | Hard | D | The grid looks like it needs visited. Strict increase makes it a DAG, so paths can safely reuse cells across branches — visited would prune correct answers. Memo, not mark | |
| 58★ | 1857. Largest Color Value in a Directed Graph | Hard | D | 26 counters carried along the topological order; the cycle check and the DP are the same pass |
EUnion-Find, the structure itself#
Union by size and path compression are not optimisations, they are the definition. Without both it is O(n) per find.
| Solved | # | Problem | Diff | Sub-variant | Why it's essential |
|---|---|---|---|---|---|
| 59★ | 990. Satisfiability of Equality Equations | Medium | E | Process every == first, then check every !=. The ordering is the algorithm | |
| 60★ | 2685. Count the Number of Complete Components | Medium | E | Per-component node count and edge count together — the arithmetic that 261 needed | |
| —↻ | 547. Number of Provinces | Medium | E | Re-solve #20 with DSU. The calibration problem: if the DSU version is longer than 20 lines, the template is wrong |
FUnion-Find for counting and grouping#
Nearly every answer here is n − components or spareEdges vs components − 1.
| Solved | # | Problem | Diff | Sub-variant | Why it's essential |
|---|---|---|---|---|---|
| 61★ | 721. Accounts Merge | Medium | F | Union on a key that is not an index (email → id map). The most common real-world shape | |
| 62★ | 947. Most Stones Removed with Same Row or Column | Medium | F | Union rows to columns, not stones to stones. Answer is n − components | |
| 63★ | 1319. Number of Operations to Make Network Connected | Medium | F | Spare edges vs components − 1. The feasibility check before the count | |
| 64★ | 2492. Minimum Score of a Path Between Two Cities | Medium | F | The path is irrelevant — anything in the component is reachable. Recognising that is the problem | |
| 65○ | 1202. Smallest String With Swaps | Medium | F | Sort within each component; indices and characters gathered separately | |
| 66○ | 839. Similar String Groups | Hard | F | O(n²·len) pairwise unions — when the quadratic build is the intended solution | |
| 67○ | 128. Longest Consecutive Sequence | Medium | F | Solvable with DSU, but the hash-set scan is O(n) and simpler. Included so you know when not to reach for DSU | |
| —↻ | 684. Redundant Connection | Medium | F | Re-solve #26 with DSU and compare to the DFS version |
GUnion-Find over time / with extra state#
Merges arrive in an order — sometimes given, sometimes chosen by you.
| Solved | # | Problem | Diff | Sub-variant | Why it's essential |
|---|---|---|---|---|---|
| 68★ | 305. Number of Islands II PRO | Hard | G | Incremental components: activate a cell, then union with up to four already-active neighbours. Free substitute: 2092, or 827 for the label-once idea | |
| 69○ | 1101. The Earliest Moment When Everyone Become Friends PRO | Medium | G | Sort by timestamp, union, stop when the count hits 1. Free substitute: 1697 | |
| 70○ | 959. Regions Cut By Slashes | Medium | G | Split each cell into four triangles — the modelling trick, not the DSU | |
| 71○ | 685. Redundant Connection II | Hard | G | Directed: two distinct failure modes (indegree 2, and a cycle) that can co-occur. Pure case analysis | |
| 72○ | 2092. Find All People With Secret | Hard | G | Union and un-union within a timestamp group — DSU with a rollback |
HMinimum spanning tree#
Cheapest way to connect everything. Not shortest path; the MST path between two nodes is often not the shortest.
| Solved | # | Problem | Diff | Sub-variant | Why it's essential |
|---|---|---|---|---|---|
| 73★ | 1584. Min Cost to Connect All Points | Medium | H | Kruskal on the complete graph (n² edges sorted) vs. Prim in O(n²) with no heap. On a dense graph Prim wins — know which you wrote and why | |
| 74○ | 1135. Connecting Cities With Minimum Cost PRO | Medium | H | Sparse Kruskal, plus the infeasibility check. Free substitute: 1584 | |
| 75○ | 1489. Find Critical and Pseudo-Critical Edges in MST | Hard | H | Run MST forcing an edge out, then in. The definition of critical/pseudo-critical made executable |
IBridges & articulation points#
One DFS with discovery times and low-links. Brute-force removal is E times slower and never necessary.
| Solved | # | Problem | Diff | Sub-variant | Why it's essential |
|---|---|---|---|---|---|
| 76★⚠︎ | 1192. Critical Connections in a Network | Hard | I | Remove-each-edge-and-recheck is O(E·(V+E)). Tarjan: low[v] > disc[u] means the edge u–v is a bridge, in one O(V+E) pass |
JFunctional graphs (out-degree exactly 1)#
Every component is a tail leading into exactly one cycle. Generic cycle detection is the wrong tool.
| Solved | # | Problem | Diff | Sub-variant | Why it's essential |
|---|---|---|---|---|---|
| 77★ | 2360. Longest Cycle in a Graph | Hard | J | Label each node with the step index at which this walk reached it; a repeat within the current walk gives the cycle length in O(1) | |
| 78★ | 2359. Find Closest Node to Given Two Nodes | Medium | J | Two walks, one distance array each, then minimise max(d1, d2) | |
| 79○ | 457. Circular Array Loop | Medium | J | Floyd on a functional graph, plus direction consistency and the length-1 exclusion | |
| 80○ | 565. Array Nesting | Medium | J | The components partition the array, so a global visited makes it a single O(n) pass |
KEulerian path#
Every edge exactly once. The greedy that works for vertices does not work for edges.
| Solved | # | Problem | Diff | Sub-variant | Why it's essential |
|---|---|---|---|---|---|
| 81★⚠︎ | 332. Reconstruct Itinerary | Hard | K | Lexicographic greedy DFS strands you: the smallest next airport can consume the only exit. Hierholzer — append the node when it has no edges left, then reverse | |
| 82○ | 753. Cracking the Safe | Hard | K | de Bruijn sequence as an Eulerian circuit on the (n−1)-prefix graph |
2.3 Templates#
JavaA — three-colour directed cycle detection.11 lines
// A — three-colour directed cycle detection.
static final int WHITE = 0, GREY = 1, BLACK = 2;
boolean hasCycle(List<Integer>[] adj, int u, int[] color) {
color[u] = GREY;
for (int v : adj[u]) {
if (color[v] == GREY) return true; // back edge
if (color[v] == WHITE && hasCycle(adj, v, color)) return true;
}
color[u] = BLACK; // finished, and safe
return false;
}JavaB — Kahn. A short order is a cycle report; no separate detection needed.15 lines
// B — Kahn. A short order is a cycle report; no separate detection needed.
int[] topo(int n, List<Integer>[] adj) {
int[] indeg = new int[n];
for (int u = 0; u < n; u++) for (int v : adj[u]) indeg[v]++;
Deque<Integer> q = new ArrayDeque<>();
for (int u = 0; u < n; u++) if (indeg[u] == 0) q.add(u);
int[] order = new int[n];
int k = 0;
while (!q.isEmpty()) {
int u = q.poll();
order[k++] = u;
for (int v : adj[u]) if (--indeg[v] == 0) q.add(v);
}
return k == n ? order : new int[0]; // empty ⇒ cycle
}JavaE — DSU. Union by size + path compression. Both, or it is not near-constant.25 lines
// E — DSU. Union by size + path compression. Both, or it is not near-constant.
final class DSU {
private final int[] parent, size;
int components;
DSU(int n) {
parent = new int[n];
size = new int[n];
components = n;
for (int i = 0; i < n; i++) { parent[i] = i; size[i] = 1; }
}
int find(int x) {
while (parent[x] != x) { parent[x] = parent[parent[x]]; x = parent[x]; }
return x;
}
boolean union(int a, int b) {
int ra = find(a), rb = find(b);
if (ra == rb) return false; // already together
if (size[ra] < size[rb]) { int t = ra; ra = rb; rb = t; }
parent[rb] = ra;
size[ra] += size[rb];
components--;
return true;
}
int sizeOf(int x) { return size[find(x)]; }
}JavaH — Kruskal. Sort, union, stop at n-1 accepted edges.29 lines
// H — Kruskal. Sort, union, stop at n-1 accepted edges.
long kruskal(int n, int[][] edges) { // edges[i] = {u, v, w}
Arrays.sort(edges, Comparator.comparingInt(e -> e[2]));
DSU dsu = new DSU(n);
long total = 0;
int used = 0;
for (int[] e : edges) {
if (dsu.union(e[0], e[1])) { total += e[2]; if (++used == n - 1) break; }
}
return used == n - 1 ? total : -1; // -1 ⇒ disconnected
}
// H — Prim, O(n^2), no heap. Correct choice when the graph is complete.
long prim(int[][] pts) {
int n = pts.length;
int[] best = new int[n];
boolean[] in = new boolean[n];
Arrays.fill(best, Integer.MAX_VALUE);
best[0] = 0;
long total = 0;
for (int it = 0; it < n; it++) {
int u = -1;
for (int v = 0; v < n; v++) if (!in[v] && (u == -1 || best[v] < best[u])) u = v;
in[u] = true;
total += best[u];
for (int v = 0; v < n; v++)
if (!in[v]) best[v] = Math.min(best[v], cost(pts, u, v));
}
return total;
}JavaI — Tarjan bridges. disc = discovery time, low = highest ancestor reachable.16 lines
// I — Tarjan bridges. disc = discovery time, low = highest ancestor reachable.
int timer = 0;
void bridges(int u, int parent, List<Integer>[] adj,
int[] disc, int[] low, List<List<Integer>> out) {
disc[u] = low[u] = ++timer;
for (int v : adj[u]) {
if (v == parent) continue;
if (disc[v] == 0) {
bridges(v, u, adj, disc, low, out);
low[u] = Math.min(low[u], low[v]);
if (low[v] > disc[u]) out.add(List.of(u, v)); // nothing below v climbs past u
} else {
low[u] = Math.min(low[u], disc[v]); // disc[v], not low[v]
}
}
}JavaK — Hierholzer. Append on the way OUT, then reverse.6 lines
// K — Hierholzer. Append on the way OUT, then reverse.
void euler(String u, Map<String, PriorityQueue<String>> adj, LinkedList<String> route) {
PriorityQueue<String> next = adj.get(u);
while (next != null && !next.isEmpty()) euler(next.poll(), adj, route);
route.addFirst(u); // post-order insert
}2.4 Failure modes#
| # | Failure | Symptom | Fix |
|---|---|---|---|
| 2.1 | visited instead of three colours | Cycles reported where none exist, or unsafe nodes called safe (802) | GREY vs BLACK are different facts. Never collapse them |
| 2.2 | Indegree decremented on push instead of pop | Nodes emitted before their prerequisites | Decrement when u is dequeued, enqueue v only at zero |
| 2.3 | Kahn's cycle case unhandled | Empty or truncated order returned as if valid | Always compare order.size() to n |
| 2.4 | DSU without union by size | Long chain input degrades to O(n) per find, TLE | Union by size and path compression; both are two lines |
| 2.5 | parent[a] = b written instead of parent[find(a)] = find(b) | Silently merges the wrong sets | Union roots, never raw nodes |
| 2.6 | components decremented on every union call | Count too low when the union was a no-op | Decrement only inside the ra != rb branch |
| 2.7 | MST used where shortest path was asked | Correct-looking output, wrong problem | MST minimises total edge weight, not any path |
| 2.8 | Kruskal without the used == n − 1 check | A disconnected graph returns a partial forest's cost | Count accepted edges and report infeasible |
| 2.9 | low[u] = min(low[u], low[v]) on a back edge | Bridges missed | Back edges use disc[v]; tree edges use low[v] |
| 2.10 | Alien Dictionary: edges from every char pair | Wrong order, or a spurious cycle | Only the first differing char of adjacent words |
| 2.11 | Alien Dictionary: prefix case ignored | Accepts ["abc","ab"] | Longer-word-first with a shared prefix is invalid, return "" |
| 2.12 | Eulerian path via plain backtracking | Exponential, or a stranded partial route | Hierholzer, post-order append, reverse at the end |
| 2.13 | Functional graph run through generic DFS cycle detection | Correct but O(n²) across all starts | Label by visit step; each node is touched once globally |