Files
codetutor/backend/data/questions/check-knight-tour-configuration.yaml

206 lines
9.3 KiB
YAML

title: Check Knight Tour Configuration
slug: check-knight-tour-configuration
difficulty: medium
leetcode_id: 2596
leetcode_url: https://leetcode.com/problems/check-knight-tour-configuration/
categories:
- arrays
- graphs
patterns:
- matrix-traversal
function_signature: "def check_valid_grid(grid: list[list[int]]) -> bool:"
test_cases:
visible:
- input: { 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]] }
expected: true
- input: { grid: [[0, 3, 6], [5, 8, 1], [2, 7, 4]] }
expected: false
hidden:
- input: { grid: [[0, 2, 4], [5, 7, 1], [6, 3, 8]] }
expected: false
- input: { grid: [[1, 0, 2], [3, 4, 5], [6, 7, 8]] }
expected: false
- input: { grid: [[0, 5, 4], [3, 2, 7], [6, 1, 8]] }
expected: false
description: |
There is a knight on an `n x n` chessboard. In a valid configuration, the knight starts **at the top-left cell** of the board and visits every cell on the board **exactly once**.
You are given an `n x n` integer matrix `grid` consisting of distinct integers from the range `[0, n * n - 1]` where `grid[row][col]` indicates that the cell `(row, col)` is the `grid[row][col]`<sup>th</sup> cell that the knight visited. The moves are **0-indexed**.
Return `true` *if* `grid` *represents a valid configuration of the knight's movements or* `false` *otherwise*.
**Note** that a valid knight move consists of moving two squares vertically and one square horizontally, or two squares horizontally and one square vertically.
constraints: |
- `n == grid.length == grid[i].length`
- `3 <= n <= 7`
- `0 <= grid[row][col] < n * n`
- All integers in `grid` are **unique**
examples:
- input: "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]]"
output: "true"
explanation: "The grid represents a valid knight's tour. Starting from position (0,0) with value 0, the knight makes valid L-shaped moves to visit every cell exactly once."
- input: "grid = [[0,3,6],[5,8,1],[2,7,4]]"
output: "false"
explanation: "The 8th move of the knight is not valid considering its position after the 7th move. The knight cannot reach the cell marked 8 from the cell marked 7 using a valid knight move."
explanation:
intuition: |
Think of this problem as verifying a recorded sequence of moves rather than finding one.
The grid tells us the **order** in which each cell was visited. Cell with value `0` was visited first, cell with value `1` was visited second, and so on. Our job is to verify that each consecutive pair of visits (from cell `k` to cell `k+1`) represents a valid knight move.
Imagine you're watching a replay of a chess game. You see where the knight was on move 0, move 1, move 2, etc. To verify it's valid, you just need to check: "Could a knight actually jump from the move-0 position to the move-1 position? From move-1 to move-2?" and so on.
A knight moves in an "L" shape: two squares in one direction and one square perpendicular to that. This gives us exactly 8 possible moves from any position. The key insight is that the **absolute difference** between row positions must be paired with a complementary difference in column positions: either (2,1) or (1,2).
approach: |
We solve this using **Position Mapping and Sequential Validation**:
**Step 1: Build a position lookup**
- Create a mapping from move number to `(row, col)` position
- Iterate through the grid and store `positions[grid[r][c]] = (r, c)` for each cell
- This lets us quickly find where the knight was on any given move
&nbsp;
**Step 2: Validate the starting position**
- Check that `grid[0][0] == 0` — the knight must start at the top-left cell
- If this fails, return `false` immediately
&nbsp;
**Step 3: Verify each consecutive move**
- For each move number from `0` to `n*n - 2`:
- Get the position of move `k` and move `k+1`
- Calculate the absolute row difference `dr` and column difference `dc`
- A valid knight move requires either `(dr, dc) = (1, 2)` or `(dr, dc) = (2, 1)`
- If any move is invalid, return `false`
&nbsp;
**Step 4: Return the result**
- If all consecutive moves are valid, return `true`
common_pitfalls:
- title: Forgetting the Starting Position Check
description: |
The problem states the knight must start at the **top-left cell**. This means `grid[0][0]` must equal `0`.
Some solutions skip this check and only validate the moves, but a grid where the knight starts elsewhere (like `grid[1][1] = 0`) would be invalid even if all moves are correct knight moves.
wrong_approach: "Only checking if consecutive moves are valid knight moves"
correct_approach: "First verify grid[0][0] == 0, then validate all moves"
- title: Checking All 8 Directions Instead of Just Validating Distances
description: |
You don't need to enumerate all 8 knight move directions and check if the next position matches one of them.
The simpler approach is to compute the absolute row and column differences between consecutive positions. A valid knight move has distances that are either `(1, 2)` or `(2, 1)`.
wrong_approach: "Generating all 8 possible next positions and checking membership"
correct_approach: "Check if (abs(dr), abs(dc)) forms a valid knight move pattern"
- title: Off-by-One in Move Iteration
description: |
The grid contains values from `0` to `n*n - 1`. When checking consecutive moves, you iterate from move `0` to move `n*n - 2` (not `n*n - 1`), comparing each move `k` with move `k+1`.
Going up to `n*n - 1` would cause an index-out-of-bounds error when accessing move `n*n`.
key_takeaways:
- "**Position mapping**: Converting grid values to coordinates enables O(1) lookup for any move number"
- "**Knight move validation**: A knight move is valid if the absolute differences in coordinates are `(1, 2)` or `(2, 1)`"
- "**Sequential verification**: When validating a path, check each consecutive pair rather than trying to reconstruct the entire path"
- "**Constraint checking**: Always verify initial conditions (starting position) before validating the sequence"
time_complexity: "O(n^2). We iterate through all n*n cells once to build the position map, then iterate through n*n - 1 consecutive pairs to validate moves."
space_complexity: "O(n^2). We store a position lookup containing n*n entries mapping move numbers to coordinates."
solutions:
- approach_name: Position Mapping
is_optimal: true
code: |
def check_valid_grid(grid: list[list[int]]) -> bool:
n = len(grid)
# Knight must start at top-left corner
if grid[0][0] != 0:
return False
# Build position lookup: move number -> (row, col)
positions = {}
for r in range(n):
for c in range(n):
positions[grid[r][c]] = (r, c)
# Verify each consecutive move is a valid knight move
for move in range(n * n - 1):
r1, c1 = positions[move]
r2, c2 = positions[move + 1]
# Calculate absolute differences
dr = abs(r2 - r1)
dc = abs(c2 - c1)
# Valid knight move: (1,2) or (2,1)
if not ((dr == 1 and dc == 2) or (dr == 2 and dc == 1)):
return False
return True
explanation: |
**Time Complexity:** O(n^2) — We scan the grid once to build the lookup, then validate n^2 - 1 moves.
**Space Complexity:** O(n^2) — The position dictionary stores one entry per cell.
We first ensure the knight starts at (0,0), then build a mapping from move numbers to positions. Finally, we check each consecutive pair of moves to verify they form valid knight moves.
- approach_name: Direct Grid Search
is_optimal: false
code: |
def check_valid_grid(grid: list[list[int]]) -> bool:
n = len(grid)
# Knight must start at top-left corner
if grid[0][0] != 0:
return False
# All 8 possible knight moves
knight_moves = [
(-2, -1), (-2, 1), (-1, -2), (-1, 2),
(1, -2), (1, 2), (2, -1), (2, 1)
]
# Find position of move 0 (must be at 0,0)
row, col = 0, 0
# Verify each move from 0 to n*n-2
for move in range(n * n - 1):
found = False
# Try all 8 knight moves to find the next position
for dr, dc in knight_moves:
nr, nc = row + dr, col + dc
# Check bounds and if this is the next move
if 0 <= nr < n and 0 <= nc < n and grid[nr][nc] == move + 1:
row, col = nr, nc
found = True
break
if not found:
return False
return True
explanation: |
**Time Complexity:** O(n^2) — We check up to 8 neighbors for each of the n^2 moves.
**Space Complexity:** O(1) — We only store the current position and constants.
This approach follows the knight move-by-move. Starting from (0,0), we search all 8 possible knight destinations to find the cell containing the next move number. While this uses less space, it requires searching neighbors at each step.