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-variant | The one idea | |
|---|---|---|
| A | Flood fill on a grid | The graph is implicit — neighbours are computed, never stored |
| B | Boundary seeding / invert the question | Start from outside and keep what you didn't reach |
| C | Grid BFS — unweighted shortest path | Levels are distances; DFS cannot do this |
| D | Multi-source BFS | Every source sits in the queue at distance 0 before the first pop |
| E | Explicit graphs — building the adjacency list | The input format is the problem; the traversal is trivial |
| F | Cycle detection in undirected graphs | Skip the parent, not the whole visited set |
| G | Bipartite / 2-colouring | Colour on push; a conflict is a same-colour edge |
| H | Traversal that copies or keys by node | visited is a Map, and it is also the memo |
| I | Enumerating paths (backtracking) | The mark must be undone — and on a DAG there is no mark at all |
| J | State-space BFS | The node is a tuple; the graph is never materialised |
| K | Word-ladder family & bidirectional BFS | Build 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 | # | Problem | Diff | Sub-variant | Why it's essential |
|---|---|---|---|---|---|
| 1★ | 733. Flood Fill | Easy | A | The atom. The same-colour guard that prevents infinite recursion when newColor == oldColor | |
| 2★ | 200. Number of Islands | Medium | A | Component counting. Sink-as-you-go vs. a separate visited[][] — know why sinking is legal here | |
| 3★ | 695. Max Area of Island | Medium | A | The DFS returns a value instead of void; 1 + sum(children) on a grid | |
| 4○ | 463. Island Perimeter | Easy | A | Not a traversal at all — count edges facing water. Included because the instinct to DFS is wrong | |
| 5○ | 1254. Number of Closed Islands | Medium | A | The border test folded into the recursive return | |
| 6★⚠︎ | 827. Making A Large Island | Hard | A | Re-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 | # | Problem | Diff | Sub-variant | Why it's essential |
|---|---|---|---|---|---|
| 7★ | 130. Surrounded Regions | Medium | B | Seed from the border, mark survivors, then flip in a second pass | |
| 8★ | 1020. Number of Enclaves | Medium | B | Same machine, count instead of flip — the pairing makes the abstraction visible | |
| 9★ | 417. Pacific Atlantic Water Flow | Medium | B | Two reverse traversals (uphill from each ocean), then intersect. Forward-from-every-cell is O((mn)²) | |
| 10○ | 1905. Count Sub Islands | Medium | B | Two 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 | # | Problem | Diff | Sub-variant | Why it's essential |
|---|---|---|---|---|---|
| 11★ | 1091. Shortest Path in Binary Matrix | Medium | C | Eight directions, and the level-size snapshot. Mark on push — the canonical demonstration | |
| 12★ | 1926. Nearest Exit from Entrance in Maze | Medium | C | The entrance is not an exit; the boundary predicate is the whole difficulty | |
| 13○ | 909. Snakes and Ladders | Medium | C | Index ↔ boustrophedon coordinate mapping is the entire problem; the BFS is four lines | |
| 14○ | 490. The Maze PRO | Medium | C | The 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 | # | Problem | Diff | Sub-variant | Why it's essential |
|---|---|---|---|---|---|
| 15★ | 994. Rotting Oranges | Medium | D | The atom. Count remaining fresh to distinguish "unreachable" from "done" | |
| 16★ | 542. 01 Matrix | Medium | D | Distance-to-nearest, not reachability. Know the two-pass DP alternative and why BFS is the safer default | |
| 17★ | 1162. As Far from Land as Possible | Medium | D | The answer is the last level reached, not a per-cell value | |
| 18★ | 934. Shortest Bridge | Medium | D | Two machines composed: DFS to identify one component, then multi-source BFS from all of it | |
| 19○ | 286. Walls and Gates PRO | Medium | D | Multi-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 | # | Problem | Diff | Sub-variant | Why it's essential |
|---|---|---|---|---|---|
| 20★ | 547. Number of Provinces | Medium | E | Adjacency matrix input; component counting without ever building a list | |
| 21★ | 841. Keys and Rooms | Medium | E | Reachability from a fixed source — the answer is "did visited fill up" | |
| 22★ | 1466. Reorder Routes to Make All Paths Lead to the City Zero | Medium | E | Store 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 Graph | Easy | E | The bare minimum; useful as a template check | |
| 24○ | 323. Number of Connected Components in an Undirected Graph PRO | Medium | E | Free substitute: 547 or 2316 | |
| 25○ | 2316. Count Unreachable Pairs of Nodes in an Undirected Graph | Medium | E | Component 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 | # | Problem | Diff | Sub-variant | Why it's essential |
|---|---|---|---|---|---|
| 26★ | 684. Redundant Connection | Medium | F | The 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 PRO | Medium | F | "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 | # | Problem | Diff | Sub-variant | Why it's essential |
|---|---|---|---|---|---|
| 28★ | 785. Is Graph Bipartite? | Medium | G | Colour on push. The outer loop over unvisited nodes is not optional | |
| 29★ | 886. Possible Bipartition | Medium | G | Identical 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 | # | Problem | Diff | Sub-variant | Why it's essential |
|---|---|---|---|---|---|
| 30★ | 133. Clone Graph | Medium | H | Map<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 | # | Problem | Diff | Sub-variant | Why it's essential |
|---|---|---|---|---|---|
| 31★⚠︎ | 797. All Paths From Source to Target | Medium | I | The 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 Search | Medium | I | Mark, recurse, unmark — exactly one unmark per mark. In-place marking beats a boolean[][] | |
| 33○ | 212. Word Search II | Hard | I | Trie-pruned backtracking; pruning dead trie branches is what makes it pass, not the trie itself | |
| 34○ | 980. Unique Paths III | Hard | I | The "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 | # | Problem | Diff | Sub-variant | Why it's essential |
|---|---|---|---|---|---|
| 35★ | 752. Open the Lock | Medium | J | The node is a 4-digit string. Deadends go into visited before the search starts | |
| 36★ | 1293. Shortest Path in a Grid with Obstacles Elimination | Hard | J | The 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 Keys | Hard | J | The node is (r, c, keyMask). Bitmask in the state — the ceiling of this sub-variant | |
| 38○ | 847. Shortest Path Visiting All Nodes | Hard | J | (node, mask) with n simultaneous sources; revisiting nodes is allowed and required | |
| 39○ | 773. Sliding Puzzle | Hard | J | The whole board serialised to a string is the node; the neighbour table is precomputed | |
| 40○ | 1345. Jump Game IV | Hard | J | Clear 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 | # | Problem | Diff | Sub-variant | Why it's essential |
|---|---|---|---|---|---|
| 41★ | 127. Word Ladder | Hard | K | Wildcard pattern buckets (h*t) beat pairwise comparison. Then the two-ended search and its termination condition | |
| 42○ | 433. Minimum Genetic Mutation | Medium | K | Same shape, smaller alphabet — the transfer test for 127 | |
| 43○ | 126. Word Ladder II | Hard | K | BFS to build the layer graph, DFS to reconstruct. Two machines, and the parents map between them | |
| 44○ | 815. Bus Routes | Hard | K | The 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#
| # | Failure | Symptom | Fix |
|---|---|---|---|
| 1.1 | visited marked on pop | TLE, or a distance larger than the true minimum | Mark on push, in the same statement that enqueues |
| 1.2 | Bounds checked after indexing | ArrayIndexOutOfBounds on row 0 | Order the guard: bounds first, then contents |
| 1.3 | Recursive DFS on a 1000×1000 grid | StackOverflowError on a snake-shaped input | Convert to an explicit stack, or use BFS |
| 1.4 | Forward traversal where reverse was intended | O((mn)²), TLE on 417 / 827 | Ask "who can reach me" instead of "who can I reach" |
| 1.5 | Missing outer loop over components | Correct on connected tests, wrong on the hidden disconnected one | Loop for s in 0..n-1: if not seen[s] in every component-level algorithm |
| 1.6 | Undirected edge added once | Half the graph is unreachable | Add both directions at build time, once, in one place |
| 1.7 | Parent skipped by node rather than by edge | False negative on a multigraph (two edges between the same pair) | Track the edge index, not the parent id, when duplicates are possible |
| 1.8 | visited keyed by position when the state carries a budget | Wrong answer, not just slow (1293, 864) | Key by the full tuple; size the array [m][n][k+1] |
| 1.9 | Backtracking without an unmark | Only the first path is found | Exactly one unmark per mark, on the line after the recursive call |
| 1.10 | Clone put in the map after recursing | Infinite recursion on a cycle (133) | Insert the shell into the map before touching neighbours |
| 1.11 | DFS used for an unweighted shortest path | A path is returned, but not the shortest | BFS. There is no DFS fix |