Graphs, No Gaps — Traversal & Connectivity · Ordering, Partitions & Spanning Structure · Weighted Paths Library
Show

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-variantThe one idea
ADirected cycle detection — three coloursvisited is not enough; "in progress" and "finished" are different facts
BKahn's algorithmIndegree-zero queue; a short output is the cycle report
CTopological sort as a modelling exerciseThe edges are not given — deriving them is the problem
DDP over a DAGAcyclic ⇒ memoise, no visited
EUnion-Find — the structure itselfUnion by size + path compression, or it is not O(α)
FUnion-Find for counting and groupingThe answer is usually n − components or edges − (n − components)
GUnion-Find over time / with extra stateMerges arrive in an order you choose or are given
HMinimum spanning treeCheapest connection, not shortest path
IBridges & articulation pointsOne DFS with low-links replaces E connectivity checks
JFunctional graphs (out-degree exactly 1)Every component is a rho; label by visit time
KEulerian pathEvery 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#ProblemDiffSub-variantWhy it's essential
45207. Course ScheduleMediumAThe atom. Both the 3-colour DFS and the Kahn answer; write both
46⚠︎802. Find Eventual Safe StatesMediumAA 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#ProblemDiffSub-variantWhy it's essential
47210. Course Schedule IIMediumBThe template. Decrement on pop, enqueue at zero, compare order.size() to n
482050. Parallel Courses IIIHardBCritical path: finish[v] = max(finish[u]) + time[v] accumulated along the topological order
491136. Parallel Courses PROMediumBThe 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#ProblemDiffSub-variantWhy it's essential
502115. Find All Possible Recipes from Given SuppliesMediumCNodes of two kinds (recipes and supplies) in one graph; indegree counted only over missing ingredients
51⚠︎269. Alien Dictionary PROHardCOnly 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
52310. Minimum Height TreesMediumCTopological 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
53444. Sequence Reconstruction PROMediumCThe order is unique iff the queue never holds more than one node
54851. Loud and RichMediumCMemoised DFS over the "richer than" DAG — the bridge to 2.D
551203. Sort Items by Groups Respecting DependenciesHardCTwo nested topological sorts, groups then items. Ungrouped items each need their own synthetic group
562392. Build a Matrix With ConditionsHardCTwo 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#ProblemDiffSub-variantWhy it's essential
57⚠︎329. Longest Increasing Path in a MatrixHardDThe 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
581857. Largest Color Value in a Directed GraphHardD26 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#ProblemDiffSub-variantWhy it's essential
59990. Satisfiability of Equality EquationsMediumEProcess every == first, then check every !=. The ordering is the algorithm
602685. Count the Number of Complete ComponentsMediumEPer-component node count and edge count together — the arithmetic that 261 needed
547. Number of ProvincesMediumERe-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#ProblemDiffSub-variantWhy it's essential
61721. Accounts MergeMediumFUnion on a key that is not an index (email → id map). The most common real-world shape
62947. Most Stones Removed with Same Row or ColumnMediumFUnion rows to columns, not stones to stones. Answer is n − components
631319. Number of Operations to Make Network ConnectedMediumFSpare edges vs components − 1. The feasibility check before the count
642492. Minimum Score of a Path Between Two CitiesMediumFThe path is irrelevant — anything in the component is reachable. Recognising that is the problem
651202. Smallest String With SwapsMediumFSort within each component; indices and characters gathered separately
66839. Similar String GroupsHardFO(n²·len) pairwise unions — when the quadratic build is the intended solution
67128. Longest Consecutive SequenceMediumFSolvable with DSU, but the hash-set scan is O(n) and simpler. Included so you know when not to reach for DSU
684. Redundant ConnectionMediumFRe-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#ProblemDiffSub-variantWhy it's essential
68305. Number of Islands II PROHardGIncremental components: activate a cell, then union with up to four already-active neighbours. Free substitute: 2092, or 827 for the label-once idea
691101. The Earliest Moment When Everyone Become Friends PROMediumGSort by timestamp, union, stop when the count hits 1. Free substitute: 1697
70959. Regions Cut By SlashesMediumGSplit each cell into four triangles — the modelling trick, not the DSU
71685. Redundant Connection IIHardGDirected: two distinct failure modes (indegree 2, and a cycle) that can co-occur. Pure case analysis
722092. Find All People With SecretHardGUnion 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#ProblemDiffSub-variantWhy it's essential
731584. Min Cost to Connect All PointsMediumHKruskal 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
741135. Connecting Cities With Minimum Cost PROMediumHSparse Kruskal, plus the infeasibility check. Free substitute: 1584
751489. Find Critical and Pseudo-Critical Edges in MSTHardHRun 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#ProblemDiffSub-variantWhy it's essential
76⚠︎1192. Critical Connections in a NetworkHardIRemove-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#ProblemDiffSub-variantWhy it's essential
772360. Longest Cycle in a GraphHardJLabel 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)
782359. Find Closest Node to Given Two NodesMediumJTwo walks, one distance array each, then minimise max(d1, d2)
79457. Circular Array LoopMediumJFloyd on a functional graph, plus direction consistency and the length-1 exclusion
80565. Array NestingMediumJThe 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#ProblemDiffSub-variantWhy it's essential
81⚠︎332. Reconstruct ItineraryHardKLexicographic 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
82753. Cracking the SafeHardKde 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#

#FailureSymptomFix
2.1visited instead of three coloursCycles reported where none exist, or unsafe nodes called safe (802)GREY vs BLACK are different facts. Never collapse them
2.2Indegree decremented on push instead of popNodes emitted before their prerequisitesDecrement when u is dequeued, enqueue v only at zero
2.3Kahn's cycle case unhandledEmpty or truncated order returned as if validAlways compare order.size() to n
2.4DSU without union by sizeLong chain input degrades to O(n) per find, TLEUnion by size and path compression; both are two lines
2.5parent[a] = b written instead of parent[find(a)] = find(b)Silently merges the wrong setsUnion roots, never raw nodes
2.6components decremented on every union callCount too low when the union was a no-opDecrement only inside the ra != rb branch
2.7MST used where shortest path was askedCorrect-looking output, wrong problemMST minimises total edge weight, not any path
2.8Kruskal without the used == n − 1 checkA disconnected graph returns a partial forest's costCount accepted edges and report infeasible
2.9low[u] = min(low[u], low[v]) on a back edgeBridges missedBack edges use disc[v]; tree edges use low[v]
2.10Alien Dictionary: edges from every char pairWrong order, or a spurious cycleOnly the first differing char of adjacent words
2.11Alien Dictionary: prefix case ignoredAccepts ["abc","ab"]Longer-word-first with a shared prefix is invalid, return ""
2.12Eulerian path via plain backtrackingExponential, or a stranded partial routeHierholzer, post-order append, reverse at the end
2.13Functional graph run through generic DFS cycle detectionCorrect but O(n²) across all startsLabel by visit step; each node is touched once globally