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
1733. Flood FillEasyAThe atom. The same-colour guard that prevents infinite recursion when newColor == oldColor
2200. Number of IslandsMediumAComponent counting. Sink-as-you-go vs. a separate visited[][] — know why sinking is legal here
3695. Max Area of IslandMediumAThe DFS returns a value instead of void; 1 + sum(children) on a grid
4463. Island PerimeterEasyANot a traversal at all — count edges facing water. Included because the instinct to DFS is wrong
51254. 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
7130. Surrounded RegionsMediumBSeed from the border, mark survivors, then flip in a second pass
81020. Number of EnclavesMediumBSame machine, count instead of flip — the pairing makes the abstraction visible
9417. Pacific Atlantic Water FlowMediumBTwo reverse traversals (uphill from each ocean), then intersect. Forward-from-every-cell is O((mn)²)
101905. 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
111091. Shortest Path in Binary MatrixMediumCEight directions, and the level-size snapshot. Mark on push — the canonical demonstration
121926. Nearest Exit from Entrance in MazeMediumCThe entrance is not an exit; the boundary predicate is the whole difficulty
13909. Snakes and LaddersMediumCIndex ↔ boustrophedon coordinate mapping is the entire problem; the BFS is four lines
14490. 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
15994. Rotting OrangesMediumDThe atom. Count remaining fresh to distinguish "unreachable" from "done"
16542. 01 MatrixMediumDDistance-to-nearest, not reachability. Know the two-pass DP alternative and why BFS is the safer default
171162. As Far from Land as PossibleMediumDThe answer is the last level reached, not a per-cell value
18934. Shortest BridgeMediumDTwo machines composed: DFS to identify one component, then multi-source BFS from all of it
19286. 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
20547. Number of ProvincesMediumEAdjacency matrix input; component counting without ever building a list
21841. Keys and RoomsMediumEReachability from a fixed source — the answer is "did visited fill up"
221466. 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
231971. Find if Path Exists in GraphEasyEThe bare minimum; useful as a template check
24323. Number of Connected Components in an Undirected Graph PROMediumEFree substitute: 547 or 2316
252316. 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
26684. 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
28785. Is Graph Bipartite?MediumGColour on push. The outer loop over unvisited nodes is not optional
29886. 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
30133. 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
3279. Word SearchMediumIMark, recurse, unmark — exactly one unmark per mark. In-place marking beats a boolean[][]
33212. Word Search IIHardITrie-pruned backtracking; pruning dead trie branches is what makes it pass, not the trie itself
34980. 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
35752. Open the LockMediumJThe node is a 4-digit string. Deadends go into visited before the search starts
361293. 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
37864. Shortest Path to Get All KeysHardJThe node is (r, c, keyMask). Bitmask in the state — the ceiling of this sub-variant
38847. Shortest Path Visiting All NodesHardJ(node, mask) with n simultaneous sources; revisiting nodes is allowed and required
39773. Sliding PuzzleHardJThe whole board serialised to a string is the node; the neighbour table is precomputed
401345. 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
41127. Word LadderHardKWildcard pattern buckets (h*t) beat pairwise comparison. Then the two-ended search and its termination condition
42433. Minimum Genetic MutationMediumKSame shape, smaller alphabet — the transfer test for 127
43126. Word Ladder IIHardKBFS to build the layer graph, DFS to reconstruct. Two machines, and the parents map between them
44815. 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