190 lines
8.7 KiB
YAML
190 lines
8.7 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
|
|
|
|
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
|
|
|
|
|
|
|
|
**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
|
|
|
|
|
|
|
|
**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`
|
|
|
|
|
|
|
|
**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.
|