Constraint problems
Constraint search problems: N-Queens, Sudoku, word search, a rat in a maze, graph coloring and Hamiltonian paths, each as choose-explore-unchoose with a validity check.
Learning goals
- Encode constraints as O(1) checks
- Trace N-Queens and read the attacked squares
- Run Sudoku with candidate lists
- Search a grid with a visited marker that is restored
- Explain why graph colouring and Hamiltonian path are hard in general
Prerequisites
N-Queens
Place n queens on an n×n board so that no two share a row, column or diagonal. Place one queen per row, top to bottom; the only decision at each row is the column. A square is unsafe if an earlier queen is in the same column or on either diagonal. Three boolean arrays answer that in O(1): usedCol[c], diag1[row − c + n − 1] (one diagonal direction) and diag2[row + c] (the other).
Run the presets: 4 queens finds [2, 4, 1, 3] after 8 placements and 4 backtracks; both solutions of N = 4 need 16 placements; the 10 solutions of N = 5 need 53; the first solution for N = 6, [2, 4, 6, 1, 3, 5], needs 31 placements and 25 backtracks. Dimmed squares are attacked by a queen above.
| n | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|
| Solutions | 2 | 10 | 4 | 40 | 92 | 352 | 724 |
1static int solve(int n, int row, int[] cols, boolean[] usedCol, boolean[] diag1, boolean[] diag2) { // counts solutions2if (row == n) return 1;3int count = 0;4for (int c = 0; c < n; c++) {5if (usedCol[c] || diag1[row - c + n - 1] || diag2[row + c]) continue; // attacked by a queen above6usedCol[c] = diag1[row - c + n - 1] = diag2[row + c] = true; cols[row] = c; // choose7count += solve(n, row + 1, cols, usedCol, diag1, diag2); // explore8usedCol[c] = diag1[row - c + n - 1] = diag2[row + c] = false; // unchoose9}10return count;11}12static int queens(int n) { return solve(n, 0, new int[n], new boolean[n], new boolean[2 * n - 1], new boolean[2 * n - 1]); }
N-Queens by backtracking
Interactive · edit the inputgrid · 4 × 4
Place one queen per row, top to bottom. A square is attacked when a queen above shares its column or a diagonal. If a row has no safe square, back up and move the previous queen. Stop at the first complete board.
1static int solve(int n, int row, int[] cols, boolean[] usedCol, boolean[] diag1, boolean[] diag2, boolean firstOnly) {2if (row == n) return 1; // all n queens placed3int count = 0;4for (int c = 0; c < n; c++) {5if (usedCol[c] || diag1[row - c + n - 1] || diag2[row + c]) continue; // attacked by a queen above6usedCol[c] = diag1[row - c + n - 1] = diag2[row + c] = true; cols[row] = c; // choose7count += solve(n, row + 1, cols, usedCol, diag1, diag2, firstOnly); // explore8if (firstOnly && count > 0) return count; // keep the board of the first solution9usedCol[c] = diag1[row - c + n - 1] = diag2[row + c] = false; // unchoose10}11return count;12}
Live state
row
0
queens
0
solutions
0
Execution trace (dry run table)Show 33 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | row=0 queens=0 solutions=0 | Place one queen per row, top to bottom. A square is attacked when a queen above shares its column or a diagonal. If a row has no safe square, back up and move the previous queen. Stop at the first complete board. |
| 2 | L6 | row=1 queens=1 solutions=0 | Row 1, column 1 is safe: place a queen. |
| 3 | L5 | row=1 queens=1 solutions=0 | Row 2, column 1: attacked by the queen at row 1, column 1. Try the next column. |
| 4 | L5 | row=1 queens=1 solutions=0 | Row 2, column 2: attacked by the queen at row 1, column 1. Try the next column. |
| 5 | L6 | row=2 queens=2 solutions=0 | Row 2, column 3 is safe: place a queen. |
| 6 | L5 | row=2 queens=2 solutions=0 | Row 3, column 1: attacked by the queen at row 1, column 1. Try the next column. |
| 7 | L5 | row=2 queens=2 solutions=0 | Row 3, column 2: attacked by the queen at row 2, column 3. Try the next column. |
| 8 | L5 | row=2 queens=2 solutions=0 | Row 3, column 3: attacked by the queen at row 1, column 1. Try the next column. |
| 9 | L5 | row=2 queens=2 solutions=0 | Row 3, column 4: attacked by the queen at row 2, column 3. Try the next column. |
| 10 | L9 | row=1 queens=1 solutions=0 | Remove the queen from row 2, column 3: back to row 2 to try the next column. |
| 11 | L6 | row=2 queens=2 solutions=0 | Row 2, column 4 is safe: place a queen. |
| 12 | L5 | row=2 queens=2 solutions=0 | Row 3, column 1: attacked by the queen at row 1, column 1. Try the next column. |
| 13 | L6 | row=3 queens=3 solutions=0 | Row 3, column 2 is safe: place a queen. |
| 14 | L5 | row=3 queens=3 solutions=0 | Row 4, column 1: attacked by the queen at row 1, column 1. Try the next column. |
| 15 | L5 | row=3 queens=3 solutions=0 | Row 4, column 2: attacked by the queen at row 2, column 4. Try the next column. |
| 16 | L5 | row=3 queens=3 solutions=0 | Row 4, column 3: attacked by the queen at row 3, column 2. Try the next column. |
| 17 | L5 | row=3 queens=3 solutions=0 | Row 4, column 4: attacked by the queen at row 1, column 1. Try the next column. |
| 18 | L9 | row=2 queens=2 solutions=0 | Remove the queen from row 3, column 2: back to row 3 to try the next column. |
| 19 | L5 | row=2 queens=2 solutions=0 | Row 3, column 3: attacked by the queen at row 1, column 1. Try the next column. |
| 20 | L5 | row=2 queens=2 solutions=0 | Row 3, column 4: attacked by the queen at row 2, column 4. Try the next column. |
| 21 | L9 | row=1 queens=1 solutions=0 | Remove the queen from row 2, column 4: back to row 2 to try the next column. |
| 22 | L9 | row=0 queens=0 solutions=0 | Remove the queen from row 1, column 1: back to row 1 to try the next column. |
| 23 | L6 | row=1 queens=1 solutions=0 | Row 1, column 2 is safe: place a queen. |
| 24 | L5 | row=1 queens=1 solutions=0 | Row 2, column 1: attacked by the queen at row 1, column 2. Try the next column. |
| 25 | L5 | row=1 queens=1 solutions=0 | Row 2, column 2: attacked by the queen at row 1, column 2. Try the next column. |
| 26 | L5 | row=1 queens=1 solutions=0 | Row 2, column 3: attacked by the queen at row 1, column 2. Try the next column. |
| 27 | L6 | row=2 queens=2 solutions=0 | Row 2, column 4 is safe: place a queen. |
| 28 | L6 | row=3 queens=3 solutions=0 | Row 3, column 1 is safe: place a queen. |
| 29 | L5 | row=3 queens=3 solutions=0 | Row 4, column 1: attacked by the queen at row 3, column 1. Try the next column. |
| 30 | L5 | row=3 queens=3 solutions=0 | Row 4, column 2: attacked by the queen at row 1, column 2. Try the next column. |
| 31 | L6 | row=4 queens=4 solutions=0 | Row 4, column 3 is safe: place a queen. |
| 32 | L2 | row=4 queens=4 solutions=1 | All 4 queens are placed safely: solution 1 is [2, 4, 1, 3] (column of each row, 1-based). |
| 33 | L11 | row=4 queens=4 solutions=1 return=[2, 4, 1, 3] | First solution: [2, 4, 1, 3]. 8 queens placed and 4 backtracks on the way. |
Sudoku solver
Sudoku fits the same template: pick an empty cell, try each digit that does not clash with its row, column or box, recurse, undo. A candidate test with three sets (or bit masks) per row, column and box makes each check O(1). The animation solves the 4×4 version with a 2×2 box, so you can follow every step; the 9×9 code is identical except for the sizes.
The “Forced moves: 4 placements instead of 12” preset shows the effect of constraint propagation, which the lesson on search improvements covers. Try “Unsolvable” to see the search exhaust every branch and report failure.
1static boolean solveSudoku(int[][] g) { // 0 = empty; returns true when solved2for (int r = 0; r < 9; r++)3for (int c = 0; c < 9; c++) {4if (g[r][c] != 0) continue;5for (int v = 1; v <= 9; v++) {6if (!legalDigit(g, r, c, v)) continue;7g[r][c] = v; // choose8if (solveSudoku(g)) return true; // explore9g[r][c] = 0; // unchoose10}11return false; // no digit fits this cell: dead end12}13return true; // no empty cell: solved14}15private static boolean legalDigit(int[][] g, int r, int c, int v) {16for (int k = 0; k < 9; k++) if (g[r][k] == v || g[k][c] == v) return false;17int br = r / 3 * 3, bc = c / 3 * 3;18for (int i = 0; i < 3; i++) for (int j = 0; j < 3; j++) if (g[br + i][bc + j] == v) return false;19return true;20}
4×4 Sudoku: cell order and constraint propagation
Interactive · edit the inputgrid · 4 × 4
12 empty cells. Strategy: take the most constrained cell, and before every guess fill every cell that has a single candidate.
1static boolean solve(int[][] g, int mode) { // 0 = first empty cell, 1 = fewest candidates, 2 = fewest candidates + forced moves2List<int[]> forced = new ArrayList<>();3if (mode == 2 && !propagate(g, forced)) { undo(g, forced); return false; } // a cell with no candidate: dead end4int[] cell = mode == 0 ? firstEmpty(g) : mostConstrained(g);5if (cell == null) return true; // no empty cell: solved6for (int v = 1; v <= 4; v++) {7if (!legal(g, cell[0], cell[1], v)) continue; // row, column or box clash8g[cell[0]][cell[1]] = v; // choose9if (solve(g, mode)) return true; // explore10g[cell[0]][cell[1]] = 0; // unchoose11}12undo(g, forced);13return false;14}
Live state
placements
0
forced
0
Execution trace (dry run table)Show 19 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | placements=0 forced=0 | 12 empty cells. Strategy: take the most constrained cell, and before every guess fill every cell that has a single candidate. |
| 2 | L4 | placements=0 forced=0 | Next cell: row 1, column 2 with 3 candidates [2, 3, 4]. |
| 3 | L8 | placements=1 forced=0 | Guess 2 at row 1, column 2. |
| 4 | L4 | placements=1 forced=0 | Next cell: row 1, column 3 with 2 candidates [3, 4]. |
| 5 | L8 | placements=2 forced=0 | Guess 3 at row 1, column 3. |
| 6 | L3 | placements=2 forced=1 | Row 1, column 4 has only one candidate, 4: fill it without guessing. |
| 7 | L3 | placements=2 forced=2 | Row 2, column 4 has only one candidate, 2: fill it without guessing. |
| 8 | L3 | placements=2 forced=3 | Row 3, column 4 has only one candidate, 3: fill it without guessing. |
| 9 | L4 | placements=2 forced=3 | Next cell: row 2, column 1 with 2 candidates [3, 4]. |
| 10 | L8 | placements=3 forced=3 | Guess 3 at row 2, column 1. |
| 11 | L3 | placements=3 forced=4 | Row 2, column 2 has only one candidate, 4: fill it without guessing. |
| 12 | L3 | placements=3 forced=5 | Row 4, column 2 has only one candidate, 3: fill it without guessing. |
| 13 | L4 | placements=3 forced=5 | Next cell: row 3, column 1 with 2 candidates [2, 4]. |
| 14 | L8 | placements=4 forced=5 | Guess 2 at row 3, column 1. |
| 15 | L3 | placements=4 forced=6 | Row 3, column 3 has only one candidate, 4: fill it without guessing. |
| 16 | L3 | placements=4 forced=7 | Row 4, column 1 has only one candidate, 4: fill it without guessing. |
| 17 | L3 | placements=4 forced=8 | Row 4, column 3 has only one candidate, 2: fill it without guessing. |
| 18 | L5 | placements=4 forced=8 | No empty cell is left: the grid is solved. |
| 19 | L13 | placements=4 forced=8 return=1234/3412/2143/4321 | Solved with 4 placements, 0 dead ends and 8 forced fills. |
Word search
Is a word present in a letter grid as a path of neighbouring cells that never reuses a cell? Try every cell as the start; from a cell that matches the next letter, mark it used, try the four neighbours, and restore it. Marking is the *choose* and restoring is the *unchoose*: forgetting to restore makes later paths see cells as blocked.
Run “Found (abcced)”: 9 cells are visited. “Cell reused: not found” asks for "abcb" on the same board; the path would need the same 'b' twice, so all 20 visits fail. Each failed branch is cut at the first wrong letter, which is the pruning.
1static boolean exist(char[][] b, String word) {2for (int r = 0; r < b.length; r++)3for (int c = 0; c < b[0].length; c++)4if (matchFrom(b, word, 0, r, c)) return true; // try every cell as the start5return false;6}7private static boolean matchFrom(char[][] b, String word, int k, int r, int c) {8if (k == word.length()) return true; // matched every letter9if (r < 0 || c < 0 || r >= b.length || c >= b[0].length || b[r][c] != word.charAt(k)) return false; // off the board or wrong letter10char saved = b[r][c]; b[r][c] = '#'; // choose: mark the cell as used11boolean found = matchFrom(b, word, k + 1, r + 1, c) || matchFrom(b, word, k + 1, r - 1, c)12|| matchFrom(b, word, k + 1, r, c + 1) || matchFrom(b, word, k + 1, r, c - 1);13b[r][c] = saved; // unchoose: restore the letter14return found;15}
Word search on a letter grid
Interactive · edit the inputgrid · 3 × 4
Find "abcced" as a path of neighbouring cells (up, down, left, right) that never reuses a cell. Try every cell as the start, then extend letter by letter, undoing the choice when a branch fails.
1static boolean exist(char[][] b, String word) {2for (int r = 0; r < b.length; r++)3for (int c = 0; c < b[0].length; c++)4if (dfs(b, word, 0, r, c)) return true; // try every cell as the start5return false;6}7static boolean dfs(char[][] b, String word, int k, int r, int c) {8if (k == word.length()) return true; // matched every letter9if (r < 0 || c < 0 || r >= b.length || c >= b[0].length || b[r][c] != word.charAt(k)) return false; // off the board or wrong letter10char saved = b[r][c]; b[r][c] = '#'; // choose: mark the cell as used11boolean found = dfs(b, word, k + 1, r + 1, c) || dfs(b, word, k + 1, r - 1, c)12|| dfs(b, word, k + 1, r, c + 1) || dfs(b, word, k + 1, r, c - 1); // explore four directions13b[r][c] = saved; // unchoose: restore the letter14return found;15}
Live state
word
abcced
Execution trace (dry run table)Show 13 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | word=abcced | Find "abcced" as a path of neighbouring cells (up, down, left, right) that never reuses a cell. Try every cell as the start, then extend letter by letter, undoing the choice when a branch fails. |
| 2 | L10 | matched=a next letter=b depth=1 | 'a' matches letter 1 of the word: mark the cell used and try the four neighbours. |
| 3 | L9 | matched=a next letter=b depth=1 | Cell (2, 1) holds 's' but the next letter is 'b': dead end. |
| 4 | L10 | matched=ab next letter=c depth=2 | 'b' matches letter 2 of the word: mark the cell used and try the four neighbours. |
| 5 | L9 | matched=ab next letter=c depth=2 | Cell (2, 2) holds 'f' but the next letter is 'c': dead end. |
| 6 | L10 | matched=abc next letter=c depth=3 | 'c' matches letter 3 of the word: mark the cell used and try the four neighbours. |
| 7 | L10 | matched=abcc next letter=e depth=4 | 'c' matches letter 4 of the word: mark the cell used and try the four neighbours. |
| 8 | L10 | matched=abcce next letter=d depth=5 | 'e' matches letter 5 of the word: mark the cell used and try the four neighbours. |
| 9 | L9 | matched=abcce next letter=d depth=5 | Cell (2, 3) is already on the path: it was marked '#' and no longer equals any letter. |
| 10 | L9 | matched=abcce next letter=d depth=5 | Cell (3, 4) holds 'e' but the next letter is 'd': dead end. |
| 11 | L10 | matched=abcced next letter=- depth=6 | 'd' matches letter 6 of the word: mark the cell used and try the four neighbours. |
| 12 | L8 | matched=abcced next letter=- depth=6 | All 6 letters are matched. |
| 13 | L4 | return=true cells visited=9 | "abcced" is on the board. |
Rat in a maze
A rat starts at the top-left of a grid of open cells and walls and wants the bottom-right. The search tries the four moves in a fixed order (Down, Left, Right, Up gives paths in alphabetical order), marks the current cell as visited, and unmarks it on the way back so other routes may pass through it. Visiting each cell at most once per path means the search finds *simple* paths, and the number of simple paths in an open grid grows very fast.
In the open 3×3 maze there are 12 simple paths from corner to corner, and the 2×2 maze has 2. The “One path” preset finds DRDR immediately. Note the difference from BFS: BFS finds one *shortest* path; backtracking enumerates *all* simple paths.
1static void paths(int[][] m, int r, int c, StringBuilder path, List<String> out) { // 1 = open, 0 = wall2int n = m.length;3if (r == n - 1 && c == n - 1) { out.add(path.toString()); return; } // reached the exit4m[r][c] = 0; // choose: mark as visited5String dirs = "DLRU"; int[] dr = {1, 0, 0, -1}, dc = {0, -1, 1, 0};6for (int k = 0; k < 4; k++) {7int nr = r + dr[k], nc = c + dc[k];8if (nr < 0 || nc < 0 || nr >= n || nc >= n || m[nr][nc] == 0) continue; // outside, a wall, or already visited9path.append(dirs.charAt(k));10paths(m, nr, nc, path, out);11path.deleteCharAt(path.length() - 1);12}13m[r][c] = 1; // unchoose: free the cell14}
Rat in a maze: all paths by backtracking
Interactive · edit the inputgrid · 3 × 3
The rat starts at the top-left and wants the bottom-right (★). It tries the moves in the order Down, Left, Right, Up, never stepping on a wall or a cell it is already using. Stop at the first path.
1static void paths(int[][] m, int r, int c, StringBuilder path, List<String> out) { // 1 = open, 0 = wall2int n = m.length;3if (r == n - 1 && c == n - 1) { out.add(path.toString()); return; } // reached the exit4m[r][c] = 0; // choose: mark as visited5String dirs = "DLRU"; int[] dr = {1, 0, 0, -1}, dc = {0, -1, 1, 0};6for (int k = 0; k < 4; k++) {7int nr = r + dr[k], nc = c + dc[k];8if (nr < 0 || nc < 0 || nr >= n || nc >= n || m[nr][nc] == 0) continue; // outside, a wall, or already visited9path.append(dirs.charAt(k)); // choose the move10paths(m, nr, nc, path, out); // explore11path.deleteCharAt(path.length() - 1); // unchoose the move12}13m[r][c] = 1; // unchoose: free the cell14}
Live state
path
(start)
paths
0
Execution trace (dry run table)Show 9 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | path=(start) paths=0 | The rat starts at the top-left and wants the bottom-right (★). It tries the moves in the order Down, Left, Right, Up, never stepping on a wall or a cell it is already using. Stop at the first path. |
| 2 | L9 | path=D paths=0 moves=1 | Move D to (2, 1). |
| 3 | L8 | path=D paths=0 moves=1 | D to (3, 1): a wall, skip. |
| 4 | L9 | path=DR paths=0 moves=2 | Move R to (2, 2). |
| 5 | L9 | path=DRD paths=0 moves=3 | Move D to (3, 2). |
| 6 | L8 | path=DRD paths=0 moves=3 | L to (3, 1): a wall, skip. |
| 7 | L9 | path=DRDR paths=0 moves=4 | Move R to (3, 3). |
| 8 | L3 | path=DRDR paths=1 moves=4 | Reached the exit: path 1 is "DRDR". |
| 9 | L14 | return=DRDR | First path found: "DRDR". |
Graph coloring
Colour the vertices of a graph with at most m colours so that adjacent vertices differ. Colour vertices in order; for vertex v try each colour not used by an already-coloured neighbour. When no colour fits, back up. Whether a graph is 3-colourable is NP-complete, so no polynomial algorithm is known; backtracking with pruning is the standard exact method.
The “Triangle plus a tail, 3 colours” preset finds [c0, c1, c2, c0] in 8 nodes. The “K4 with 3 colours: impossible” preset exhausts the search: four mutually adjacent vertices need four colours. Symmetry breaking (see search improvements) cuts that search from 49 to 12 nodes.
1static boolean color(int[][] adj, int v, int[] colors, int m) { // colors[] starts filled with -12if (v == adj.length) return true; // every vertex is coloured3for (int c = 0; c < m; c++) {4if (conflict(adj, v, c, colors)) continue; // a neighbour already has colour c5colors[v] = c; // choose6if (color(adj, v + 1, colors, m)) return true; // explore7colors[v] = -1; // unchoose8}9return false;10}11private static boolean conflict(int[][] adj, int v, int c, int[] colors) {12for (int u : adj[v]) if (colors[u] == c) return true;13return false;14}
Graph coloring with m colors
Interactive · edit the inputpartial solution
solutions found (0)
edges
Colour 4 vertices with 3 colours so that joined vertices differ. Vertices are coloured in order; a colour is rejected if a lower-numbered neighbour already uses it. With symmetry breaking only one new colour is opened at a time, because renaming colours never changes whether a colouring exists.
1static boolean color(int[][] adj, int v, int[] colors, int m, boolean symmetry) { // colors[] starts filled with -12if (v == adj.length) return true; // every vertex is coloured3int maxUsed = -1; for (int c : colors) maxUsed = Math.max(maxUsed, c);4for (int c = 0; c < m; c++) {5if (symmetry && c > maxUsed + 1) break; // colours are interchangeable: open at most one new colour6if (conflict(adj, v, c, colors)) continue; // a neighbour already has colour c7colors[v] = c; // choose8if (color(adj, v + 1, colors, m, symmetry)) return true; // explore9colors[v] = -1; // unchoose10}11return false;12}
Live state
depth
0
solutions
0
Call stack (top first)
- empty
Execution trace (dry run table)Show 15 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | depth=0 solutions=0 | Colour 4 vertices with 3 colours so that joined vertices differ. Vertices are coloured in order; a colour is rejected if a lower-numbered neighbour already uses it. With symmetry breaking only one new colour is opened at a time, because renaming colours never changes whether a colouring exists. |
| 2 | L1 | depth=1 solutions=0 | Explore colours [·, ·, ·, ·]. |
| 3 | L7 | depth=1 solutions=0 | Choose v0=c0. |
| 4 | L1 | depth=2 solutions=0 | Explore colours [c0, ·, ·, ·]. |
| 5 | L6 | depth=3 solutions=0 | Reject v1=c0: neighbour v0 already has colour c0. |
| 6 | L7 | depth=2 solutions=0 | Choose v1=c1. |
| 7 | L1 | depth=3 solutions=0 | Explore colours [c0, c1, ·, ·]. |
| 8 | L6 | depth=4 solutions=0 | Reject v2=c0: neighbour v0 already has colour c0. |
| 9 | L6 | depth=4 solutions=0 | Reject v2=c1: neighbour v1 already has colour c1. |
| 10 | L7 | depth=3 solutions=0 | Choose v2=c2. |
| 11 | L1 | depth=4 solutions=0 | Explore colours [c0, c1, c2, ·]. |
| 12 | L7 | depth=4 solutions=0 | Choose v3=c0. |
| 13 | L1 | depth=5 solutions=0 | Explore colours [c0, c1, c2, c0]. |
| 14 | L2 | depth=5 solutions=1 | Complete and valid: record [c0, c1, c2, c0]. |
| 15 | L12 | solutions=1 nodes=8 return=[c0, c1, c2, c0] | Search finished: 1 solution, 7 choices tried. Backtracking explores every branch the pruning rules allow. |
Hamiltonian path basic search
A **Hamiltonian path** visits every vertex exactly once. The search starts at a vertex and repeatedly extends the path to an unvisited neighbour; if every neighbour is visited or leads to a dead end, back up. It is the permutation problem restricted by the graph's edges. Like colouring it is NP-complete: the worst case is factorial, and no efficient algorithm is known for general graphs.
The “Dead end first” preset tries 0→1 first, gets stuck, and finds 0 → 2 → 3 → 1 → 4 after 17 nodes. A Hamiltonian path is different from an Eulerian path, which uses every *edge* once and *can* be found in linear time.
1static boolean hamiltonian(int[][] adj, List<Integer> path, boolean[] seen) { // path starts with the start vertex2if (path.size() == adj.length) return true; // visited every vertex exactly once3int last = path.get(path.size() - 1);4for (int u : adj[last]) {5if (seen[u]) continue; // already on the path6seen[u] = true; path.add(u); // choose7if (hamiltonian(adj, path, seen)) return true; // explore8path.remove(path.size() - 1); seen[u] = false; // unchoose9}10return false;11}
Hamiltonian path from vertex 0
Interactive · edit the inputpartial solution
solutions found (0)
edges
Find a path from vertex 0 that visits all 4 vertices exactly once. From the last vertex try each neighbour that is not yet on the path; if every neighbour fails, back up. No polynomial algorithm is known for this problem, so exhaustive search is the baseline.
1static boolean ham(int[][] adj, List<Integer> path, boolean[] seen) {2if (path.size() == adj.length) return true; // visited every vertex exactly once3int last = path.get(path.size() - 1);4for (int u : adj[last]) {5if (seen[u]) continue; // already on the path6seen[u] = true; path.add(u); // choose7if (ham(adj, path, seen)) return true; // explore8path.remove(path.size() - 1); seen[u] = false; // unchoose9}10return false;11}
Live state
depth
0
solutions
0
Call stack (top first)
- empty
Execution trace (dry run table)Show 13 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | depth=0 solutions=0 | Find a path from vertex 0 that visits all 4 vertices exactly once. From the last vertex try each neighbour that is not yet on the path; if every neighbour fails, back up. No polynomial algorithm is known for this problem, so exhaustive search is the baseline. |
| 2 | L1 | depth=1 solutions=0 | Explore 0. |
| 3 | L6 | depth=1 solutions=0 | Choose 1. |
| 4 | L1 | depth=2 solutions=0 | Explore 0 → 1. |
| 5 | L5 | depth=3 solutions=0 | Reject 0: vertex 0 is already on the path. |
| 6 | L6 | depth=2 solutions=0 | Choose 2. |
| 7 | L1 | depth=3 solutions=0 | Explore 0 → 1 → 2. |
| 8 | L5 | depth=4 solutions=0 | Reject 0: vertex 0 is already on the path. |
| 9 | L5 | depth=4 solutions=0 | Reject 1: vertex 1 is already on the path. |
| 10 | L6 | depth=3 solutions=0 | Choose 3. |
| 11 | L1 | depth=4 solutions=0 | Explore 0 → 1 → 2 → 3. |
| 12 | L2 | depth=4 solutions=1 | Complete and valid: record 0 → 1 → 2 → 3. |
| 13 | L10 | solutions=1 nodes=7 return=0 → 1 → 2 → 3 | Search finished: 1 solution, 6 choices tried. Backtracking explores every branch the pruning rules allow. |
Practice ladder
Now try it yourself
Easy
- N-Queens II (count only, n = 4)
- Flood Fill
Medium
- N-Queens
- Word Search
- Rat in a Maze
Advanced
- Sudoku Solver
- Word Search II
- Unique Paths III
- Graph Coloring (m-colouring problem)
Key takeaways
- Constraint problems add a validity check to choose-explore-unchoose; encode the check in O(1).
- Mark-and-restore is the unchoose for grid searches.
- Colouring and Hamiltonian path are NP-complete: backtracking is exact but can be exponential.