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

PATTERN 1 — TRAVERSAL & CONNECTIVITY#

The machine: mark on push, visit once, never revisit. Everything in this pattern is O(V+E), and the only design decisions are what a node is and what you carry.

1.1 Sub-variant map#

Sub-variantThe one idea
AFlood fill on a gridThe graph is implicit — neighbours are computed, never stored
BBoundary seeding / invert the questionStart from outside and keep what you didn't reach
CGrid BFS — unweighted shortest pathLevels are distances; DFS cannot do this
DMulti-source BFSEvery source sits in the queue at distance 0 before the first pop
EExplicit graphs — building the adjacency listThe input format is the problem; the traversal is trivial
FCycle detection in undirected graphsSkip the parent, not the whole visited set
GBipartite / 2-colouringColour on push; a conflict is a same-colour edge
HTraversal that copies or keys by nodevisited is a Map, and it is also the memo
IEnumerating paths (backtracking)The mark must be undone — and on a DAG there is no mark at all
JState-space BFSThe node is a tuple; the graph is never materialised
KWord-ladder family & bidirectional BFSBuild the neighbour buckets once; expand the smaller frontier

1.2 Problems#

AFlood fill on a grid#

Neighbours are computed, not stored. The four-direction loop and the in-bounds guard are one unit.

Solved#ProblemDiffSub-variantWhy it's essential
1★733. Flood FillEasyAThe atom. The same-colour guard that prevents infinite recursion when newColor == oldColor
2★200. Number of IslandsMediumAComponent counting. Sink-as-you-go vs. a separate visited[][] — know why sinking is legal here
3★695. Max Area of IslandMediumAThe DFS returns a value instead of void; 1 + sum(children) on a grid
4○463. Island PerimeterEasyANot a traversal at all — count edges facing water. Included because the instinct to DFS is wrong
5○1254. Number of Closed IslandsMediumAThe border test folded into the recursive return
6★⚠︎827. Making A Large IslandHardARe-flooding from every 0 is O((mn)²). Label each island once with an id → size map, then sum distinct neighbour ids

BBoundary seeding / invert the question#

You cannot mark "enclosed" directly. Mark "escapes", then take the complement.

Solved#ProblemDiffSub-variantWhy it's essential
7★130. Surrounded RegionsMediumBSeed from the border, mark survivors, then flip in a second pass
8★1020. Number of EnclavesMediumBSame machine, count instead of flip — the pairing makes the abstraction visible
9★417. Pacific Atlantic Water FlowMediumBTwo reverse traversals (uphill from each ocean), then intersect. Forward-from-every-cell is O((mn)²)
10○1905. Count Sub IslandsMediumBTwo grids in lockstep; the disqualifying cell must not short-circuit the traversal

CGrid BFS, unweighted shortest path#

A BFS level is a distance. DFS finds a path; only BFS finds the shortest one.

Solved#ProblemDiffSub-variantWhy it's essential
11★1091. Shortest Path in Binary MatrixMediumCEight directions, and the level-size snapshot. Mark on push — the canonical demonstration
12★1926. Nearest Exit from Entrance in MazeMediumCThe entrance is not an exit; the boundary predicate is the whole difficulty
13○909. Snakes and LaddersMediumCIndex ↔ boustrophedon coordinate mapping is the entire problem; the BFS is four lines
14○490. The Maze PROMediumCThe edge is a roll to the wall, not a step. Free substitute: 1926 with a modified neighbour function

DMulti-source BFS#

Put every source in the queue before the first pop and the levels come out right for free.

Solved#ProblemDiffSub-variantWhy it's essential
15★994. Rotting OrangesMediumDThe atom. Count remaining fresh to distinguish "unreachable" from "done"
16★542. 01 MatrixMediumDDistance-to-nearest, not reachability. Know the two-pass DP alternative and why BFS is the safer default
17★1162. As Far from Land as PossibleMediumDThe answer is the last level reached, not a per-cell value
18★934. Shortest BridgeMediumDTwo machines composed: DFS to identify one component, then multi-source BFS from all of it
19○286. Walls and Gates PROMediumDMulti-source in its purest form. Free substitute: 542

EExplicit graphs, building the adjacency list#

The traversal is trivial. Converting int[][] edges or an n × n matrix into List<Integer>[] is the skill.

Solved#ProblemDiffSub-variantWhy it's essential
20★547. Number of ProvincesMediumEAdjacency matrix input; component counting without ever building a list
21★841. Keys and RoomsMediumEReachability from a fixed source — the answer is "did visited fill up"
22★1466. Reorder Routes to Make All Paths Lead to the City ZeroMediumEStore both directions with a sign, traverse ignoring direction, count the wrong-way edges. The single most reusable trick in this sub-variant
23○1971. Find if Path Exists in GraphEasyEThe bare minimum; useful as a template check
24○323. Number of Connected Components in an Undirected Graph PROMediumEFree substitute: 547 or 2316
25○2316. Count Unreachable Pairs of Nodes in an Undirected GraphMediumEComponent sizes, and the running-sum trick that avoids O(k²) over components

FCycle detection in undirected graphs#

Skip the parent edge, not the parent node. And a cycle-free graph is a forest, not necessarily a tree.

Solved#ProblemDiffSub-variantWhy it's essential
26★684. Redundant ConnectionMediumFThe first edge whose endpoints already share a component. The DSU version is the point; the DFS version is the check
27★⚠︎261. Graph Valid Tree PROMediumF"No cycle" is only half the answer — a forest is acyclic. Needs edges == n − 1 and exactly one component. Free substitute: 2685, or 1319 with n − 1 asserted by hand

GBipartite / 2-colouring#

A conflict is an edge between two nodes of the same colour. Iterate over all components — the graph may be disconnected.

Solved#ProblemDiffSub-variantWhy it's essential
28★785. Is Graph Bipartite?MediumGColour on push. The outer loop over unvisited nodes is not optional
29★886. Possible BipartitionMediumGIdentical machine on a graph you build yourself — the recognition step is the whole exercise

HTraversal that copies or keys by node#

visited stops being a boolean array and becomes a map — and the map is simultaneously the memo and the output.

Solved#ProblemDiffSub-variantWhy it's essential
30★133. Clone GraphMediumHMap<Node, Node>: put the clone in the map before recursing on neighbours, or a cycle recurses forever

IEnumerating paths (backtracking on a graph)#

Shortest path uses a permanent mark. Enumerating paths must undo it. On a DAG there is no mark at all.

Solved#ProblemDiffSub-variantWhy it's essential
31★⚠︎797. All Paths From Source to TargetMediumIThe input is a DAG, so a visited set is not merely unnecessary — it is wrong, silently dropping every path through an already-seen node
32★79. Word SearchMediumIMark, recurse, unmark — exactly one unmark per mark. In-place marking beats a boolean[][]
33○212. Word Search IIHardITrie-pruned backtracking; pruning dead trie branches is what makes it pass, not the trie itself
34○980. Unique Paths IIIHardIThe "must cover every empty cell" counter carried down and restored

JState-space BFS#

The node is a tuple. Nothing is materialised; you write neighbours(state) and let BFS do the rest.

Solved#ProblemDiffSub-variantWhy it's essential
35★752. Open the LockMediumJThe node is a 4-digit string. Deadends go into visited before the search starts
36★1293. Shortest Path in a Grid with Obstacles EliminationHardJThe node is (r, c, k). visited[r][c] alone gives a wrong answer, not just a slow one
37★864. Shortest Path to Get All KeysHardJThe node is (r, c, keyMask). Bitmask in the state — the ceiling of this sub-variant
38○847. Shortest Path Visiting All NodesHardJ(node, mask) with n simultaneous sources; revisiting nodes is allowed and required
39○773. Sliding PuzzleHardJThe whole board serialised to a string is the node; the neighbour table is precomputed
40○1345. Jump Game IVHardJClear the value → indices bucket after using it once, or the same bucket is scanned O(n) times

KWord-ladder family & bidirectional BFS#

Building the neighbour relation costs more than the search. Then expand whichever frontier is smaller.

Solved#ProblemDiffSub-variantWhy it's essential
41★127. Word LadderHardKWildcard pattern buckets (h*t) beat pairwise comparison. Then the two-ended search and its termination condition
42○433. Minimum Genetic MutationMediumKSame shape, smaller alphabet — the transfer test for 127
43○126. Word Ladder IIHardKBFS to build the layer graph, DFS to reconstruct. Two machines, and the parents map between them
44○815. Bus RoutesHardKThe route is the node, not the stop. Model-choice is the entire problem

1.3 Templates#

JavaA/B — grid DFS. Sink as you go; the visited array is the grid itself.9 lines
// A/B — grid DFS. Sink as you go; the visited array is the grid itself.
static final int[][] DIRS = {{1,0},{-1,0},{0,1},{0,-1}};

int dfs(char[][] g, int r, int c) {
    if (r < 0 || r >= g.length || c < 0 || c >= g[0].length || g[r][c] != '1') return 0;
    g[r][c] = '0';                       // mark BEFORE recursing
    int size = 1;
    for (int[] d : DIRS) size += dfs(g, r + d[0], c + d[1]);
    return size;
}
JavaC — grid BFS. Distance is the level index. Mark on push.22 lines
// C — grid BFS. Distance is the level index. Mark on push.
int bfs(int[][] g, int sr, int sc, int tr, int tc) {
    int m = g.length, n = g[0].length;
    boolean[][] seen = new boolean[m][n];
    Deque<int[]> q = new ArrayDeque<>();
    q.add(new int[]{sr, sc});
    seen[sr][sc] = true;                 // on push, not on pop
    for (int dist = 0; !q.isEmpty(); dist++) {
        for (int sz = q.size(); sz > 0; sz--) {
            int[] cur = q.poll();
            if (cur[0] == tr && cur[1] == tc) return dist;
            for (int[] d : DIRS) {
                int nr = cur[0] + d[0], nc = cur[1] + d[1];
                if (nr < 0 || nr >= m || nc < 0 || nc >= n) continue;
                if (seen[nr][nc] || g[nr][nc] == 1) continue;
                seen[nr][nc] = true;
                q.add(new int[]{nr, nc});
            }
        }
    }
    return -1;
}
JavaD — multi-source BFS. Every source is seeded before the first pop.6 lines
// D — multi-source BFS. Every source is seeded before the first pop.
Deque<int[]> q = new ArrayDeque<>();
for (int r = 0; r < m; r++)
    for (int c = 0; c < n; c++)
        if (isSource(g[r][c])) { q.add(new int[]{r, c}); seen[r][c] = true; }
// ...then the identical level loop as above.
JavaE — adjacency list from an edge list. Undirected adds both directions.10 lines
// E — adjacency list from an edge list. Undirected adds both directions.
List<Integer>[] build(int n, int[][] edges, boolean directed) {
    List<Integer>[] adj = new List[n];
    for (int i = 0; i < n; i++) adj[i] = new ArrayList<>();
    for (int[] e : edges) {
        adj[e[0]].add(e[1]);
        if (!directed) adj[e[1]].add(e[0]);
    }
    return adj;
}
JavaF — undirected cycle detection. Skip the parent, not the visited set.11 lines
// F — undirected cycle detection. Skip the parent, not the visited set.
boolean hasCycle(List<Integer>[] adj, int u, int parent, boolean[] seen) {
    seen[u] = true;
    for (int v : adj[u]) {
        if (v == parent) continue;                     // the one exception
        if (seen[v]) return true;
        if (hasCycle(adj, v, u, seen)) return true;
    }
    return false;
}
// A graph is a TREE iff edges == n - 1 AND one DFS from node 0 marks every node.
JavaG — bipartite check. 0 = unvisited, 1 / -1 = the two colours.17 lines
// G — bipartite check. 0 = unvisited, 1 / -1 = the two colours.
boolean isBipartite(List<Integer>[] adj, int n) {
    int[] color = new int[n];
    for (int s = 0; s < n; s++) {
        if (color[s] != 0) continue;                   // disconnected components
        Deque<Integer> q = new ArrayDeque<>(List.of(s));
        color[s] = 1;
        while (!q.isEmpty()) {
            int u = q.poll();
            for (int v : adj[u]) {
                if (color[v] == color[u]) return false;
                if (color[v] == 0) { color[v] = -color[u]; q.add(v); }
            }
        }
    }
    return true;
}
JavaJ — state-space BFS. Only the encode/decode changes between problems.18 lines
// J — state-space BFS. Only the encode/decode changes between problems.
int bfs(String start, String target, Set<String> blocked) {
    if (blocked.contains(start)) return -1;
    Set<String> seen = new HashSet<>(List.of(start));
    Deque<String> q = new ArrayDeque<>(List.of(start));
    for (int dist = 0; !q.isEmpty(); dist++) {
        for (int sz = q.size(); sz > 0; sz--) {
            String cur = q.poll();
            if (cur.equals(target)) return dist;
            for (String nxt : neighbours(cur)) {
                if (seen.contains(nxt) || blocked.contains(nxt)) continue;
                seen.add(nxt);
                q.add(nxt);
            }
        }
    }
    return -1;
}

1.4 Failure modes#

#FailureSymptomFix
1.1visited marked on popTLE, or a distance larger than the true minimumMark on push, in the same statement that enqueues
1.2Bounds checked after indexingArrayIndexOutOfBounds on row 0Order the guard: bounds first, then contents
1.3Recursive DFS on a 1000×1000 gridStackOverflowError on a snake-shaped inputConvert to an explicit stack, or use BFS
1.4Forward traversal where reverse was intendedO((mn)²), TLE on 417 / 827Ask "who can reach me" instead of "who can I reach"
1.5Missing outer loop over componentsCorrect on connected tests, wrong on the hidden disconnected oneLoop for s in 0..n-1: if not seen[s] in every component-level algorithm
1.6Undirected edge added onceHalf the graph is unreachableAdd both directions at build time, once, in one place
1.7Parent skipped by node rather than by edgeFalse negative on a multigraph (two edges between the same pair)Track the edge index, not the parent id, when duplicates are possible
1.8visited keyed by position when the state carries a budgetWrong answer, not just slow (1293, 864)Key by the full tuple; size the array [m][n][k+1]
1.9Backtracking without an unmarkOnly the first path is foundExactly one unmark per mark, on the line after the recursive call
1.10Clone put in the map after recursingInfinite recursion on a cycle (133)Insert the shell into the map before touching neighbours
1.11DFS used for an unweighted shortest pathA path is returned, but not the shortestBFS. There is no DFS fix