2664. The Knight’s Tour 🔒
Problem Description
This problem asks you to simulate a knight's tour on a chess board. Given a board of dimensions m x n
and a starting position (r, c)
, you need to find a path where the knight visits every cell exactly once.
The knight moves in an "L" shape, just like in chess. From any position (r1, c1)
, the knight can move to position (r2, c2)
if:
- The destination is within the board boundaries:
0 <= r2 <= m - 1
and0 <= c2 <= n - 1
- The move forms an L-shape:
min(abs(r1 - r2), abs(c1 - c2)) = 1
andmax(abs(r1 - r2), abs(c1 - c2)) = 2
This means the knight moves either:
- 2 squares in one direction and 1 square perpendicular to it, or
- 1 square in one direction and 2 squares perpendicular to it
Your task is to return a 2D array where each cell contains a number indicating the order in which that cell was visited. The starting position should have value 0
, the first move should lead to a cell with value 1
, the second move to value 2
, and so on.
For example, with a 3 x 4
board starting at position (0, 0)
:
- The knight starts at
(0, 0)
- marked as0
- Moves to
(1, 2)
- marked as1
- Moves to
(2, 0)
- marked as2
- And continues until all 12 cells are visited
The problem guarantees that at least one valid solution exists for the given inputs, so you don't need to handle impossible cases.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- No: While the knight moves on a board which could be viewed as a graph, the problem is fundamentally about finding a specific path/sequence, not about graph traversal properties like shortest paths or connectivity.
Need to solve for kth smallest/largest?
- No: The problem is not about finding the kth smallest or largest element. We need to find a complete tour visiting all cells.
Involves Linked Lists?
- No: The problem involves a 2D array/board, not linked lists.
Does the problem have small constraints?
- Yes: The constraints explicitly state
1 <= m, n <= 5
, meaning the board size is at most 5x5 = 25 cells. This is a very small constraint that makes exhaustive search feasible.
Brute force / Backtracking?
- Yes: Given the small constraints and the need to find a valid path that visits all cells exactly once, backtracking is the ideal approach. We need to:
- Try different knight moves from each position
- Mark cells as visited
- Backtrack when we hit a dead end (can't make a valid move)
- Continue until we find a complete tour
Conclusion: The flowchart correctly leads us to use a Backtracking approach. The small board size (max 5x5) and the requirement to find a specific valid path through all cells makes this a classic backtracking problem where we explore possibilities and undo moves when they don't lead to a solution.
Intuition
The knight's tour is a classic backtracking problem. Think of it like solving a maze where you can only move in L-shaped patterns, and you must visit every square exactly once.
The key insight is that we need to explore all possible paths systematically. When we place the knight on a square, we have up to 8 possible moves (the 8 L-shaped moves a knight can make). We need to try each move and see if it leads to a complete tour.
Here's the thought process:
-
Start with the given position and mark it as visited (step 0).
-
For each position, we try all 8 possible knight moves. A knight move means going 2 squares in one direction and 1 square perpendicular to it. These moves can be represented as coordinate changes:
(-2,-1), (-2,1), (-1,-2), (-1,2), (1,-2), (1,2), (2,-1), (2,1)
. -
The recursive nature: From each valid position, we recursively try to complete the tour. If we successfully place the knight on a new square, we continue from there with step count incremented by 1.
-
The backtracking part: If we reach a dead end (no valid moves available but haven't visited all squares), we need to "undo" our last move and try a different path. This is why we mark a cell as visited when we enter it and unmark it when we backtrack.
-
Success condition: We know we've found a valid tour when the current step number equals
m * n - 1
(since we start from step 0).
The beauty of backtracking here is that it guarantees we'll find a solution if one exists. We systematically explore all possibilities, and the small board size (max 5x5) makes this exhaustive search practical. The algorithm essentially builds the solution incrementally, abandoning partial solutions that cannot possibly lead to a complete tour.
Learn more about Backtracking patterns.
Solution Approach
The implementation uses depth-first search with backtracking to find a valid knight's tour. Let's walk through the key components:
Data Structure Setup:
- Create a 2D array
g
of sizem x n
initialized with-1
to track the visit order - Set
g[r][c] = 0
to mark the starting position - Use a boolean flag
ok
to indicate when we've found a complete tour
The DFS Function:
The core logic is in the dfs(i, j)
function that explores from position (i, j)
:
-
Base Case Check:
- If
g[i][j] == m * n - 1
, we've visited all cells (numbered from 0 tom*n-1
) - Set
ok = True
and return to signal success
- If
-
Knight Move Generation:
- The clever use of
pairwise((-2, -1, 2, 1, -2, 1, 2, -1, -2))
generates the 8 possible knight moves - This creates pairs:
(-2,-1), (-1,2), (2,1), (1,-2), (-2,1), (1,2), (2,-1), (-1,-2)
- Each pair
(a, b)
represents the row and column offset for a knight move
- The clever use of
-
Exploring Moves: For each possible move to position
(x, y)
:- Check if the move is valid:
0 <= x < m and 0 <= y < n
- Check if the cell is unvisited:
g[x][y] == -1
- If valid, mark the cell:
g[x][y] = g[i][j] + 1
(increment the step count) - Recursively call
dfs(x, y)
to continue the tour from the new position
- Check if the move is valid:
-
- After the recursive call, check if
ok
isTrue
- If yes, we've found a solution, so return immediately (early termination)
- If no, reset the cell:
g[x][y] = -1
to try other paths
- After the recursive call, check if
-
Optimization with Early Termination:
- Once
ok
becomesTrue
, all recursive calls return immediately - This prevents unnecessary exploration after finding the first valid solution
- Once
The algorithm starts by calling dfs(r, c)
from the initial position. The backtracking ensures we explore all possible paths until we find one that visits every cell exactly once. The final board g
contains the visit order, which is then returned as the solution.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with a 3×3 board starting at position (0,0).
Initial Setup:
- Board dimensions: 3×3 (9 cells total)
- Starting position: (0,0)
- Initialize board with -1s, then mark starting position as 0:
[0] [-1] [-1] [-1] [-1] [-1] [-1] [-1] [-1]
Step 1: From (0,0), explore knight moves
Possible moves from (0,0):
- (0+2, 0+1) = (2,1) ✓ Valid
- (0+1, 0+2) = (1,2) ✓ Valid
- All other knight moves go out of bounds
Try move to (2,1):
[0] [-1] [-1] [-1] [-1] [-1] [-1] [1] [-1]
Step 2: From (2,1), continue exploring
Possible moves from (2,1):
- (2-2, 1-1) = (0,0) ✗ Already visited
- (2-2, 1+1) = (0,2) ✓ Valid
- (2-1, 1-2) = (1,-1) ✗ Out of bounds
- (2-1, 1+2) = (1,3) ✗ Out of bounds
- Continue checking... only (0,2) is valid
Move to (0,2):
[0] [-1] [2] [-1] [-1] [-1] [-1] [1] [-1]
Step 3: From (0,2), continue
Valid moves: (1,0) and (2,1) (already visited)
Move to (1,0):
[0] [-1] [2] [3] [-1] [-1] [-1] [1] [-1]
Step 4: From (1,0), continue
Valid moves: (2,2) and (0,2) (already visited)
Move to (2,2):
[0] [-1] [2] [3] [-1] [-1] [-1] [1] [4]
Step 5: From (2,2), dead end!
Only valid moves are to already visited cells. BACKTRACK!
Reset (2,2) to -1 and return to (1,0). No other moves from (1,0). BACKTRACK again to (0,2), then to (2,1), then to (0,0).
Try alternative path from (0,0):
This time, try (1,2) instead of (2,1):
[0] [-1] [-1] [-1] [-1] [1] [-1] [-1] [-1]
Continue this process... Eventually find a valid tour:
[0] [3] [6] [7] [4] [1] [2] [5] [8]
Key Observations:
-
When we hit a dead end (can't visit all cells), we backtrack by:
- Resetting the current cell to -1
- Returning to the previous position
- Trying the next available move
-
The algorithm systematically explores all possibilities through DFS
-
Once we successfully place step 8 (the 9th cell, since we start from 0), we've visited all cells and found a complete tour
-
The
ok
flag prevents further exploration once a solution is found
This example demonstrates how backtracking allows us to explore different paths and undo moves when we reach dead ends, ultimately finding a valid knight's tour that visits every cell exactly once.
Solution Implementation
1class Solution:
2 def tourOfKnight(self, m: int, n: int, r: int, c: int) -> List[List[int]]:
3 """
4 Find a knight's tour on an m×n chessboard starting from position (r, c).
5 Returns a 2D grid where each cell contains the move number (0-indexed).
6
7 Args:
8 m: Number of rows in the board
9 n: Number of columns in the board
10 r: Starting row position
11 c: Starting column position
12
13 Returns:
14 2D list representing the board with move numbers
15 """
16
17 def backtrack(row: int, col: int) -> None:
18 """
19 Recursively attempt to complete the knight's tour using backtracking.
20
21 Args:
22 row: Current row position
23 col: Current column position
24 """
25 nonlocal tour_found
26
27 # Check if we've visited all squares (0-indexed, so last move is m*n-1)
28 if board[row][col] == m * n - 1:
29 tour_found = True
30 return
31
32 # Knight moves: 8 possible L-shaped moves
33 # Using pairwise to create (dx, dy) pairs from the sequence
34 knight_moves = (-2, -1, 2, 1, -2, 1, 2, -1, -2)
35
36 for dx, dy in pairwise(knight_moves):
37 next_row = row + dx
38 next_col = col + dy
39
40 # Check if the next position is valid and unvisited
41 if (0 <= next_row < m and
42 0 <= next_col < n and
43 board[next_row][next_col] == -1):
44
45 # Mark the square with the move number
46 board[next_row][next_col] = board[row][col] + 1
47
48 # Recursively explore from the new position
49 backtrack(next_row, next_col)
50
51 # If a complete tour is found, stop backtracking
52 if tour_found:
53 return
54
55 # Backtrack: unmark the square if no solution found
56 board[next_row][next_col] = -1
57
58 # Initialize the board with -1 (unvisited)
59 board = [[-1] * n for _ in range(m)]
60
61 # Mark starting position as move 0
62 board[r][c] = 0
63
64 # Flag to track if a complete tour is found
65 tour_found = False
66
67 # Start the backtracking search
68 backtrack(r, c)
69
70 return board
71
1class Solution {
2 // Grid to track knight's tour path
3 private int[][] grid;
4 // Dimensions of the board
5 private int rows;
6 private int cols;
7 // Flag to indicate if a valid tour is found
8 private boolean tourFound;
9
10 /**
11 * Finds a knight's tour starting from position (r, c) on an m x n board.
12 * Returns a 2D array where each cell contains the move number (0-indexed).
13 * If no complete tour exists, cells not visited will contain -1.
14 *
15 * @param m Number of rows in the board
16 * @param n Number of columns in the board
17 * @param r Starting row position
18 * @param c Starting column position
19 * @return 2D array representing the knight's tour
20 */
21 public int[][] tourOfKnight(int m, int n, int r, int c) {
22 this.rows = m;
23 this.cols = n;
24 this.grid = new int[m][n];
25
26 // Initialize all cells to -1 (unvisited)
27 for (int[] row : grid) {
28 Arrays.fill(row, -1);
29 }
30
31 // Mark starting position as move 0
32 grid[r][c] = 0;
33
34 // Start depth-first search from the starting position
35 dfs(r, c);
36
37 return grid;
38 }
39
40 /**
41 * Performs depth-first search to find a valid knight's tour.
42 * Uses backtracking to explore all possible paths.
43 *
44 * @param currentRow Current row position of the knight
45 * @param currentCol Current column position of the knight
46 */
47 private void dfs(int currentRow, int currentCol) {
48 // Check if all cells have been visited (tour complete)
49 if (grid[currentRow][currentCol] == rows * cols - 1) {
50 tourFound = true;
51 return;
52 }
53
54 // Knight moves: 8 possible L-shaped moves
55 // Array represents pairs of (rowOffset, colOffset) for each move
56 int[] moveOffsets = {-2, -1, 2, 1, -2, 1, 2, -1, -2};
57
58 // Try all 8 possible knight moves
59 for (int moveIndex = 0; moveIndex < 8; moveIndex++) {
60 int nextRow = currentRow + moveOffsets[moveIndex];
61 int nextCol = currentCol + moveOffsets[moveIndex + 1];
62
63 // Check if the next position is valid and unvisited
64 if (nextRow >= 0 && nextRow < rows &&
65 nextCol >= 0 && nextCol < cols &&
66 grid[nextRow][nextCol] == -1) {
67
68 // Mark the next cell with the move number
69 grid[nextRow][nextCol] = grid[currentRow][currentCol] + 1;
70
71 // Recursively explore from the new position
72 dfs(nextRow, nextCol);
73
74 // If a complete tour is found, stop exploring
75 if (tourFound) {
76 return;
77 }
78
79 // Backtrack: unmark the cell if this path doesn't lead to a solution
80 grid[nextRow][nextCol] = -1;
81 }
82 }
83 }
84}
85
1class Solution {
2public:
3 vector<vector<int>> tourOfKnight(int m, int n, int r, int c) {
4 // Initialize the board with -1 (unvisited cells)
5 vector<vector<int>> board(m, vector<int>(n, -1));
6
7 // Mark starting position with move number 0
8 board[r][c] = 0;
9
10 // Knight's 8 possible moves: (row offset, column offset)
11 // Stored as consecutive pairs in a single array
12 int knightMoves[9] = {-2, -1, 2, 1, -2, 1, 2, -1, -2};
13
14 // Flag to indicate if a complete tour is found
15 bool tourFound = false;
16
17 // Recursive DFS function to find knight's tour
18 function<void(int, int)> dfs = [&](int row, int col) {
19 // Check if we've visited all cells (complete tour)
20 if (board[row][col] == m * n - 1) {
21 tourFound = true;
22 return;
23 }
24
25 // Try all 8 possible knight moves
26 for (int moveIdx = 0; moveIdx < 8; ++moveIdx) {
27 int nextRow = row + knightMoves[moveIdx];
28 int nextCol = col + knightMoves[moveIdx + 1];
29
30 // Check if the next position is valid and unvisited
31 if (nextRow >= 0 && nextRow < m &&
32 nextCol >= 0 && nextCol < n &&
33 board[nextRow][nextCol] == -1) {
34
35 // Mark the cell with the next move number
36 board[nextRow][nextCol] = board[row][col] + 1;
37
38 // Recursively explore from the new position
39 dfs(nextRow, nextCol);
40
41 // If tour is found, stop backtracking
42 if (tourFound) {
43 return;
44 }
45
46 // Backtrack: reset the cell to unvisited
47 board[nextRow][nextCol] = -1;
48 }
49 }
50 };
51
52 // Start the DFS from the initial position
53 dfs(r, c);
54
55 // Return the board with the knight's tour (or partial tour if incomplete)
56 return board;
57 }
58};
59
1/**
2 * Finds a knight's tour on an m×n chessboard starting from position (r, c).
3 * A knight's tour is a sequence of moves where the knight visits every square exactly once.
4 *
5 * @param m - Number of rows in the chessboard
6 * @param n - Number of columns in the chessboard
7 * @param r - Starting row position of the knight
8 * @param c - Starting column position of the knight
9 * @returns A 2D array where each cell contains the move number (0 to m*n-1), or -1 if not visited
10 */
11function tourOfKnight(m: number, n: number, r: number, c: number): number[][] {
12 // Initialize the chessboard with -1 (unvisited cells)
13 const board: number[][] = Array.from({ length: m }, () => Array(n).fill(-1));
14
15 // Knight move directions: 8 possible L-shaped moves
16 // Each pair (dirs[i], dirs[i+1]) represents (deltaRow, deltaCol)
17 const knightMoves: number[] = [-2, -1, 2, 1, -2, 1, 2, -1, -2];
18
19 // Flag to track if a complete tour has been found
20 let tourFound: boolean = false;
21
22 /**
23 * Depth-first search to find a valid knight's tour
24 * @param row - Current row position
25 * @param col - Current column position
26 */
27 const dfs = (row: number, col: number): void => {
28 // Check if we've visited all squares (tour complete)
29 if (board[row][col] === m * n - 1) {
30 tourFound = true;
31 return;
32 }
33
34 // Try all 8 possible knight moves
35 for (let moveIndex = 0; moveIndex < 8; moveIndex++) {
36 const nextRow: number = row + knightMoves[moveIndex];
37 const nextCol: number = col + knightMoves[moveIndex + 1];
38
39 // Check if the next position is valid and unvisited
40 if (nextRow >= 0 && nextRow < m &&
41 nextCol >= 0 && nextCol < n &&
42 board[nextRow][nextCol] === -1) {
43
44 // Mark the next cell with the move number
45 board[nextRow][nextCol] = board[row][col] + 1;
46
47 // Recursively explore from the new position
48 dfs(nextRow, nextCol);
49
50 // If tour is found, stop backtracking
51 if (tourFound) {
52 return;
53 }
54
55 // Backtrack: unmark the cell if no valid tour found
56 board[nextRow][nextCol] = -1;
57 }
58 }
59 };
60
61 // Mark the starting position as move 0
62 board[r][c] = 0;
63
64 // Start the depth-first search from the initial position
65 dfs(r, c);
66
67 return board;
68}
69
Time and Space Complexity
Time Complexity: O(8^(m × n))
The algorithm uses backtracking with DFS to find a valid knight's tour on an m × n
board. At each position, the knight can potentially move to up to 8 different positions (the 8 L-shaped moves a knight can make in chess). In the worst case, the algorithm explores all possible paths before finding a solution or determining no solution exists.
- Each cell on the board can be visited once during a single path exploration
- The total number of cells is
m × n
- At each step, we have at most 8 choices for the next move
- The backtracking tree can have a depth of up to
m × n - 1
(since we start from one cell) - This gives us a worst-case time complexity of
O(8^(m × n))
Space Complexity: O(m × n)
The space complexity consists of:
- The 2D grid
g
which stores the visit order:O(m × n)
- The recursive call stack depth which can go up to
m × n
levels deep in the worst case:O(m × n)
- Additional variables like
ok
, loop variables, and coordinates:O(1)
Therefore, the total space complexity is O(m × n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Inefficient Knight Move Representation
The current implementation uses pairwise((-2, -1, 2, 1, -2, 1, 2, -1, -2))
which is clever but can be confusing and error-prone. If the sequence is incorrect, it won't generate all 8 valid knight moves.
Better approach:
# More explicit and maintainable knight_moves = [ (-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2), (2, -1), (2, 1) ] for dx, dy in knight_moves: next_row = row + dx next_col = col + dy # ... rest of the logic
2. Stack Overflow for Large Boards
The recursive DFS approach can cause stack overflow for larger boards (e.g., 8×8 or bigger) due to deep recursion. Python's default recursion limit is around 1000, which may be insufficient.
Solutions:
- Increase recursion limit:
sys.setrecursionlimit(m * n + 100)
- Use iterative approach with explicit stack
- Implement Warnsdorff's heuristic to reduce search space
3. Performance Issues Without Heuristics
Pure backtracking explores moves in a fixed order, which can be extremely slow for certain board configurations. The algorithm might explore millions of dead-end paths before finding a solution.
Optimization with Warnsdorff's Rule:
def count_unvisited_neighbors(row, col):
"""Count how many unvisited squares a knight can reach from (row, col)"""
count = 0
for dx, dy in knight_moves:
next_row, next_col = row + dx, col + dy
if (0 <= next_row < m and 0 <= next_col < n and
board[next_row][next_col] == -1):
count += 1
return count
# In backtrack function, sort moves by accessibility
valid_moves = []
for dx, dy in knight_moves:
next_row = row + dx
next_col = col + dy
if (0 <= next_row < m and 0 <= next_col < n and
board[next_row][next_col] == -1):
# Store move with its accessibility count
accessibility = count_unvisited_neighbors(next_row, next_col)
valid_moves.append((accessibility, next_row, next_col))
# Sort by accessibility (visit squares with fewer onward options first)
valid_moves.sort()
for _, next_row, next_col in valid_moves:
board[next_row][next_col] = board[row][col] + 1
backtrack(next_row, next_col)
# ... rest of backtracking logic
4. Missing Early Path Validation
For certain starting positions on small boards, finding a valid tour might be impossible or take excessive time. The current implementation doesn't detect obviously impossible cases early.
Quick validation check:
def is_tour_possible(m, n, r, c):
"""Quick heuristic check for obviously impossible cases"""
# For very small boards (< 6 cells), tours are often impossible
if m * n < 6:
return False
# For 3×3 board, no complete tour exists
if m == 3 and n == 3:
return False
# For certain corner starts on small boards, tours may not exist
# Add specific known impossible cases
return True
5. Memory Usage for Very Large Boards
While not typically an issue for chess-sized boards, very large boards could consume significant memory storing the full grid.
Space-efficient alternative for path tracking:
# Instead of storing the full board, store only the path
path = [(r, c)] # List of coordinates in visit order
# Convert path to board representation only at the end
def path_to_board(path, m, n):
board = [[-1] * n for _ in range(m)]
for step, (row, col) in enumerate(path):
board[row][col] = step
return board
What is the best way of checking if an element exists in a sorted array once in terms of time complexity? Select the best that applies.
Recommended Readings
Backtracking Template Prereq DFS with States problems dfs_with_states Combinatorial search problems Combinatorial search problems involve finding groupings and assignments of objects that satisfy certain conditions Finding all permutations combinations subsets and solving Sudoku are classic combinatorial problems The time complexity of combinatorial problems often grows rapidly with the size of
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!