LeetCode 2596 - Check Knight Tour Configuration
The problem gives us an n x n matrix called grid, where every number from 0 to n n - 1 appears exactly once. Each value represents the order in which a knight visited that cell during a tour of the chessboard. A knight in chess moves in an L-shape.
Difficulty: 🟡 Medium
Topics: Array, Depth-First Search, Breadth-First Search, Matrix, Simulation
Solution
Problem Understanding
The problem gives us an n x n matrix called grid, where every number from 0 to n * n - 1 appears exactly once. Each value represents the order in which a knight visited that cell during a tour of the chessboard.
A knight in chess moves in an L-shape. From position (r, c), it can move to any of these eight possible positions:
(r + 2, c + 1)(r + 2, c - 1)(r - 2, c + 1)(r - 2, c - 1)(r + 1, c + 2)(r + 1, c - 2)(r - 1, c + 2)(r - 1, c - 2)
The task is to determine whether the matrix describes a valid knight's tour configuration.
A valid configuration must satisfy two conditions:
- The knight must start at the top-left corner
(0, 0), meaninggrid[0][0]must equal0. - Every consecutive move must follow the legal movement rules of a knight.
The input guarantees that:
- Every number is unique.
- Every value lies within the valid range.
- The board size is between
3and7.
These constraints are small. Even the largest board contains only 49 cells. This tells us we do not need sophisticated optimization techniques. A direct simulation approach is sufficient.
Several edge cases are important:
- If
grid[0][0] != 0, the answer is immediatelyfalsebecause the knight must start at the top-left cell. - Consecutive positions may not form a legal knight move.
- Small boards such as
3 x 3often contain invalid tours because a full knight tour is impossible on many small dimensions. - Even though all numbers are unique and valid, the ordering itself may still violate knight movement rules.
Approaches
Brute Force Approach
A brute-force strategy would attempt to reconstruct the knight's movement sequence step by step by repeatedly searching the entire board for the next move number.
For example, to find where move 0 occurred, we scan the matrix. Then we scan again for move 1, again for move 2, and so on until n * n - 1.
After locating each consecutive pair of positions, we verify whether the knight can legally move between them.
This approach works because it explicitly checks every move in the sequence. If all consecutive moves are valid and the starting position is correct, then the configuration is valid.
However, this method is inefficient because every lookup requires scanning the whole board. Since there are n^2 moves and each search costs O(n^2), the total complexity becomes O(n^4).
Although the constraints are small enough that this still passes, it is unnecessarily slow.
Optimal Approach
The key observation is that each number uniquely identifies a board position. Instead of repeatedly searching for positions, we can preprocess the board once and directly map each move number to its coordinates.
We create an array where:
position[k] = (row, col)
This allows us to instantly retrieve the coordinates of move k.
Then we simply iterate through all consecutive moves:
0 -> 1
1 -> 2
2 -> 3
...
For each pair, we compute the row difference and column difference. A knight move is valid if:
(abs(dr), abs(dc)) == (2, 1) or (1, 2)
This reduces the overall complexity to O(n^2).
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n^4) | O(1) | Repeatedly scans the board to find each move |
| Optimal | O(n^2) | O(n^2) | Stores positions for direct lookup of each move |
Algorithm Walkthrough
- First, verify that the knight starts at the required position.
The problem explicitly states that the knight must start at the top-left cell. Therefore, if grid[0][0] != 0, we immediately return False.
2. Create a position mapping array.
We allocate an array of size n * n, where each index represents a move number. The value stored at that index will be the coordinates of that move.
Example:
positions[5] = (0, 3)
means the knight visited cell (0, 3) during move number 5.
3. Traverse the board once to populate the mapping.
For every cell (r, c):
- Read
move_number = grid[r][c] - Store:
positions[move_number] = (r, c)
After this step, every move number can be accessed in constant time. 4. Validate every consecutive knight move.
Iterate from 0 to n * n - 2.
For each move k:
- Retrieve current position
(r1, c1) - Retrieve next position
(r2, c2)
Compute:
dr = abs(r1 - r2)
dc = abs(c1 - c2)
A move is valid only if:
(dr == 2 and dc == 1)
or
(dr == 1 and dc == 2)
If neither condition holds, return False.
5. If all moves are valid, return True.
Why it works
The algorithm works because every move number uniquely identifies exactly one board position. By storing all positions in advance, we can directly verify every consecutive move in the tour.
A knight's tour is valid if and only if:
- The knight starts at
(0, 0) - Every consecutive move follows knight movement rules
The algorithm checks both conditions exactly once, so the result is correct.
Python Solution
from typing import List
class Solution:
def checkValidGrid(self, grid: List[List[int]]) -> bool:
n = len(grid)
# The knight must start at the top-left corner
if grid[0][0] != 0:
return False
# positions[k] = (row, col) of move k
positions = [None] * (n * n)
for row in range(n):
for col in range(n):
move_number = grid[row][col]
positions[move_number] = (row, col)
# Check every consecutive move
for move in range(n * n - 1):
r1, c1 = positions[move]
r2, c2 = positions[move + 1]
row_diff = abs(r1 - r2)
col_diff = abs(c1 - c2)
if not (
(row_diff == 2 and col_diff == 1)
or
(row_diff == 1 and col_diff == 2)
):
return False
return True
The implementation begins by checking whether the starting cell contains 0. If not, the function immediately returns False.
Next, the code creates a positions array where each index corresponds to a move number. By traversing the board once, we store the coordinates of every move.
After preprocessing, the algorithm iterates through all consecutive move numbers. For each pair, it computes the absolute row and column differences. A move is valid only if the differences match one of the two legal knight movement patterns.
If any move fails validation, the function returns False immediately. Otherwise, after all checks succeed, the function returns True.
Go Solution
func checkValidGrid(grid [][]int) bool {
n := len(grid)
// The knight must start at the top-left corner
if grid[0][0] != 0 {
return false
}
// positions[k] = {row, col}
positions := make([][2]int, n*n)
for row := 0; row < n; row++ {
for col := 0; col < n; col++ {
moveNumber := grid[row][col]
positions[moveNumber] = [2]int{row, col}
}
}
// Check every consecutive move
for move := 0; move < n*n-1; move++ {
r1, c1 := positions[move][0], positions[move][1]
r2, c2 := positions[move+1][0], positions[move+1][1]
rowDiff := abs(r1 - r2)
colDiff := abs(c1 - c2)
valid :=
(rowDiff == 2 && colDiff == 1) ||
(rowDiff == 1 && colDiff == 2)
if !valid {
return false
}
}
return true
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
The Go implementation follows the same logic as the Python version. Since Go does not provide a built-in integer abs function for plain integers, we define a small helper function.
The position mapping uses a slice of fixed-size arrays:
[][2]int
This efficiently stores coordinate pairs without additional allocation overhead.
Go slices are initialized with zero values automatically, so no explicit initialization is required beyond make.
Worked Examples
Example 1
grid =
[
[0,11,16,5,20],
[17,4,19,10,15],
[12,1,8,21,6],
[3,18,23,14,9],
[24,13,2,7,22]
]
Step 1: Build Position Mapping
| Move | Position |
|---|---|
| 0 | (0, 0) |
| 1 | (2, 1) |
| 2 | (4, 2) |
| 3 | (3, 0) |
| 4 | (1, 1) |
| 5 | (0, 3) |
| ... | ... |
Step 2: Validate Consecutive Moves
| From | To | Row Diff | Col Diff | Valid |
|---|---|---|---|---|
| (0,0) | (2,1) | 2 | 1 | Yes |
| (2,1) | (4,2) | 2 | 1 | Yes |
| (4,2) | (3,0) | 1 | 2 | Yes |
| (3,0) | (1,1) | 2 | 1 | Yes |
| (1,1) | (0,3) | 1 | 2 | Yes |
All moves satisfy knight movement rules, so the answer is:
true
Example 2
grid =
[
[0,3,6],
[5,8,1],
[2,7,4]
]
Step 1: Build Position Mapping
| Move | Position |
|---|---|
| 0 | (0, 0) |
| 1 | (1, 2) |
| 2 | (2, 0) |
| 3 | (0, 1) |
| 4 | (2, 2) |
| 5 | (1, 0) |
| 6 | (0, 2) |
| 7 | (2, 1) |
| 8 | (1, 1) |
Step 2: Validate Consecutive Moves
| From | To | Row Diff | Col Diff | Valid |
|---|---|---|---|---|
| (0,0) | (1,2) | 1 | 2 | Yes |
| (1,2) | (2,0) | 1 | 2 | Yes |
| (2,0) | (0,1) | 2 | 1 | Yes |
| (0,1) | (2,2) | 2 | 1 | Yes |
| (2,2) | (1,0) | 1 | 2 | Yes |
| (1,0) | (0,2) | 1 | 2 | Yes |
| (0,2) | (2,1) | 2 | 1 | Yes |
| (2,1) | (1,1) | 1 | 0 | No |
The final move is not a legal knight move, so the answer is:
false
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n^2) | We traverse the board once and validate all moves once |
| Space | O(n^2) | The position mapping stores every cell coordinate |
The board contains n^2 cells. Constructing the position mapping requires visiting each cell exactly once. The validation loop also processes each move exactly once. Therefore, the total running time is linear in the number of cells.
The extra space comes from storing the coordinate mapping for every move number.
Test Cases
from typing import List
class Solution:
def checkValidGrid(self, grid: List[List[int]]) -> bool:
n = len(grid)
if grid[0][0] != 0:
return False
positions = [None] * (n * n)
for r in range(n):
for c in range(n):
positions[grid[r][c]] = (r, c)
for move in range(n * n - 1):
r1, c1 = positions[move]
r2, c2 = positions[move + 1]
dr = abs(r1 - r2)
dc = abs(c1 - c2)
if not ((dr == 2 and dc == 1) or (dr == 1 and dc == 2)):
return False
return True
sol = Solution()
# Example 1, valid knight tour
assert sol.checkValidGrid([
[0,11,16,5,20],
[17,4,19,10,15],
[12,1,8,21,6],
[3,18,23,14,9],
[24,13,2,7,22]
]) == True
# Example 2, invalid final move
assert sol.checkValidGrid([
[0,3,6],
[5,8,1],
[2,7,4]
]) == False
# Invalid because starting position is not 0
assert sol.checkValidGrid([
[1,0,2],
[3,4,5],
[6,7,8]
]) == False
# Invalid because consecutive move is impossible
assert sol.checkValidGrid([
[0,1,2],
[3,4,5],
[6,7,8]
]) == False
# Small valid configuration
assert sol.checkValidGrid([
[0,7,2],
[5,4,1],
[8,3,6]
]) == False # still invalid due to knight movement
# Larger invalid board with one broken transition
assert sol.checkValidGrid([
[0,11,16,5,20],
[17,4,19,10,15],
[12,1,8,21,6],
[3,18,23,14,9],
[24,13,7,2,22]
]) == False
| Test | Why |
|---|---|
| Valid 5x5 tour | Confirms correct handling of a proper knight tour |
| Invalid 3x3 example | Verifies rejection of illegal move sequences |
| Starting cell not zero | Checks required starting condition |
| Sequential numbers board | Ensures simple invalid movement is detected |
| Small tricky configuration | Tests movement validation on small boards |
| Larger board with broken transition | Verifies failure detection in later moves |
Edge Cases
Starting Cell Is Not Zero
One common mistake is forgetting that the knight must start at the top-left corner. Even if all knight moves are otherwise valid, the configuration must still be rejected if grid[0][0] != 0.
The implementation handles this immediately with an early return before any additional processing.
Invalid Move Appears Late in the Sequence
A board may contain many valid knight moves before eventually failing near the end of the sequence. Naive implementations that stop checking too early or incorrectly assume validity after partial verification may produce incorrect answers.
The algorithm explicitly checks every consecutive move from 0 through n * n - 2, ensuring that even the final transition is validated.
Small Board Sizes
Small boards such as 3 x 3 are especially tricky because many configurations appear structured but do not represent valid knight tours.
The implementation does not rely on assumptions about board size or known tour existence properties. Instead, it directly validates the actual move sequence, which guarantees correctness for all valid constraints.