Facebook Pixel

2596. Check Knight Tour Configuration

Problem Description

You have an n x n chessboard with a knight piece. The knight must start at the top-left cell (position [0][0]) and visit every cell on the board exactly once.

You're given an n x n integer matrix grid where each cell contains a distinct integer from 0 to n * n - 1. The value grid[row][col] represents the order in which the knight visited that cell (0-indexed). For example, if grid[2][3] = 5, it means the cell at position (2, 3) was the 6th cell visited by the knight (since we're counting from 0).

A knight moves in an "L" shape: either 2 squares in one direction and 1 square perpendicular to that, or 1 square in one direction and 2 squares perpendicular. This gives the knight 8 possible moves from any position.

Your task is to determine if the given grid represents a valid knight's tour. Return true if:

  1. The knight starts at the top-left cell (0, 0) - meaning grid[0][0] = 0
  2. Each move from one cell to the next follows valid knight movement rules
  3. Every cell is visited exactly once

Return false otherwise.

For example, if the knight is at position (2, 3) and the next position in the sequence is (4, 4), this would be valid because the knight moved 2 squares down and 1 square right. However, if the next position were (3, 4), this would be invalid as it only moves 1 square in each direction, which isn't a valid knight move.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: The knight's tour problem can be modeled as a graph where each cell on the chessboard is a node, and edges exist between cells that are reachable by a valid knight move.

Is it a tree?

  • No: The graph is not a tree because there can be multiple paths between cells (a knight can potentially reach the same cell through different routes).

Is the problem related to directed acyclic graphs?

  • No: The problem is not specifically about DAGs. While we're checking a sequence of moves, the knight's movement graph itself can have cycles.

Is the problem related to shortest paths?

  • No: We're not looking for the shortest path. We need to verify if a given sequence of moves forms a valid knight's tour.

Does the problem involve connectivity?

  • No: We're not checking if all nodes are connected or finding connected components. We're validating a specific path.

Does the problem have small constraints?

  • Yes: The chessboard size is typically small (n x n where n is usually ≀ 100), and we need to check if the given configuration represents a valid traversal path.

DFS/backtracking?

  • Yes: DFS is appropriate here. While the solution doesn't explicitly use DFS traversal, the problem inherently involves validating a path through the graph (checking if consecutive positions in the tour follow valid knight moves), which is a graph traversal validation that aligns with DFS concepts.

Conclusion: The flowchart suggests using a DFS/backtracking approach. Although the provided solution uses a simulation approach to validate the path, it's essentially checking the validity of a depth-first traversal through the knight's movement graph where each step must follow the knight's movement rules.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that we need to verify if the knight followed valid L-shaped moves throughout its tour. Instead of trying to reconstruct or search for a valid path (which would be computationally expensive), we can simply validate the given path.

Think of it this way: we're given a numbered sequence showing the order in which the knight visited each cell. If this is a valid knight's tour, then between any two consecutive numbers in the sequence (like positions 5 and 6), the cells must be exactly one knight's move apart.

The validation approach emerges from realizing that:

  1. We first need to check if the knight started at the correct position - the top-left corner (0,0) should be marked as position 0
  2. We need to extract the visiting order - create a mapping from visit number to cell coordinates
  3. For each consecutive pair of visits, we verify if they form a valid knight move

A valid knight move means the difference in coordinates (dx, dy) between two cells must satisfy: either dx = 1 and dy = 2, or dx = 2 and dy = 1. This represents the L-shaped movement pattern of a knight.

Rather than exploring all possible knight's tours (which would be exponentially complex), we're simply checking if the given tour respects the movement rules. This transforms a potentially complex path-finding problem into a simple validation problem - we just walk through the given sequence and verify each step follows the rules.

The beauty of this approach is its simplicity: we convert the 2D grid into a linear sequence based on visit order, then check if each consecutive pair in this sequence represents a valid knight move. If any move is invalid, we immediately know the configuration is wrong. If all moves are valid, the configuration represents a legitimate knight's tour.

Learn more about Depth-First Search and Breadth-First Search patterns.

Solution Approach

The implementation follows a simulation approach to validate the knight's tour configuration:

Step 1: Initial Check First, we verify that the knight starts at the top-left corner by checking if grid[0][0] == 0. If not, we immediately return False since a valid tour must begin at position (0, 0).

Step 2: Build Position Mapping We create an array pos of size n * n to store the coordinates of each visited cell in chronological order. We iterate through the entire grid and for each cell (i, j), we store its coordinates at index grid[i][j] in the pos array.

pos = [None] * (n * n)
for i in range(n):
    for j in range(n):
        pos[grid[i][j]] = (i, j)

After this step, pos[k] contains the coordinates of the cell that was visited at step k.

Step 3: Validate Knight Moves We use Python's pairwise function to iterate through consecutive pairs of positions in the pos array. For each pair (x1, y1) and (x2, y2), we calculate the absolute differences in coordinates:

  • dx = abs(x1 - x2)
  • dy = abs(y1 - y2)

A valid knight move must satisfy one of these conditions:

  • Move 1 square horizontally and 2 squares vertically: dx == 1 and dy == 2
  • Move 2 squares horizontally and 1 square vertically: dx == 2 and dy == 1
for (x1, y1), (x2, y2) in pairwise(pos):
    dx, dy = abs(x1 - x2), abs(y1 - y2)
    ok = (dx == 1 and dy == 2) or (dx == 2 and dy == 1)
    if not ok:
        return False

Step 4: Return Result If all consecutive moves are valid knight moves, we return True. If any move violates the knight's movement rules, we return False immediately.

The time complexity is O(nΒ²) for building the position array and checking all moves, and the space complexity is O(nΒ²) for storing the position mapping.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small 3x3 grid example to illustrate the solution approach:

grid = [[0, 3, 6],
        [5, 8, 1],
        [2, 7, 4]]

Step 1: Initial Check We verify that grid[0][0] == 0. βœ“ The knight starts at the top-left corner.

Step 2: Build Position Mapping We create a pos array to store where each numbered visit occurred:

  • grid[0][0] = 0 β†’ pos[0] = (0, 0)
  • grid[0][1] = 3 β†’ pos[3] = (0, 1)
  • grid[0][2] = 6 β†’ pos[6] = (0, 2)
  • grid[1][0] = 5 β†’ pos[5] = (1, 0)
  • grid[1][1] = 8 β†’ pos[8] = (1, 1)
  • grid[1][2] = 1 β†’ pos[1] = (1, 2)
  • grid[2][0] = 2 β†’ pos[2] = (2, 0)
  • grid[2][1] = 7 β†’ pos[7] = (2, 1)
  • grid[2][2] = 4 β†’ pos[4] = (2, 2)

After building, pos = [(0,0), (1,2), (2,0), (0,1), (2,2), (1,0), (0,2), (2,1), (1,1)]

This represents the knight's path in order: (0,0) β†’ (1,2) β†’ (2,0) β†’ (0,1) β†’ (2,2) β†’ (1,0) β†’ (0,2) β†’ (2,1) β†’ (1,1)

Step 3: Validate Knight Moves We check each consecutive pair:

  1. (0,0) β†’ (1,2): dx=1, dy=2 βœ“ Valid knight move
  2. (1,2) β†’ (2,0): dx=1, dy=2 βœ“ Valid knight move
  3. (2,0) β†’ (0,1): dx=2, dy=1 βœ“ Valid knight move
  4. (0,1) β†’ (2,2): dx=2, dy=1 βœ“ Valid knight move
  5. (2,2) β†’ (1,0): dx=1, dy=2 βœ“ Valid knight move
  6. (1,0) β†’ (0,2): dx=1, dy=2 βœ“ Valid knight move
  7. (0,2) β†’ (2,1): dx=2, dy=1 βœ“ Valid knight move
  8. (2,1) β†’ (1,1): dx=1, dy=0 βœ— Invalid! Not an L-shaped move

Step 4: Return Result Since move 8 is invalid (moving only 1 square up with no horizontal movement), we return False.

For contrast, if the last move had been from (2,1) to (0,0) instead, that would have dx=2, dy=1, making it valid. If all moves had been valid L-shaped knight moves, we would return True.

Solution Implementation

1class Solution:
2    def checkValidGrid(self, grid: List[List[int]]) -> bool:
3        # Check if the knight starts at position (0, 0) with value 0
4        if grid[0][0] != 0:
5            return False
6      
7        n = len(grid)
8      
9        # Create a position mapping: value -> (row, col)
10        # This stores the coordinates of each number from 0 to n*n-1
11        positions = [None] * (n * n)
12        for row in range(n):
13            for col in range(n):
14                value = grid[row][col]
15                positions[value] = (row, col)
16      
17        # Check if consecutive numbers form valid knight moves
18        # A knight move is valid if it moves 2 squares in one direction and 1 in perpendicular
19        for i in range(len(positions) - 1):
20            current_pos = positions[i]
21            next_pos = positions[i + 1]
22          
23            # Calculate the absolute difference in row and column positions
24            row_diff = abs(current_pos[0] - next_pos[0])
25            col_diff = abs(current_pos[1] - next_pos[1])
26          
27            # Check if it's a valid knight move: (1,2) or (2,1) pattern
28            is_valid_move = (row_diff == 1 and col_diff == 2) or (row_diff == 2 and col_diff == 1)
29          
30            if not is_valid_move:
31                return False
32      
33        return True
34
1class Solution {
2    /**
3     * Checks if a grid represents a valid knight's tour path.
4     * A knight's tour is valid if it starts at position 0 and each subsequent
5     * move follows the knight's movement pattern in chess (L-shaped moves).
6     * 
7     * @param grid 2D array where grid[i][j] represents the order of visit (0 to n*n-1)
8     * @return true if the grid represents a valid knight's tour, false otherwise
9     */
10    public boolean checkValidGrid(int[][] grid) {
11        // Knight's tour must start at position 0
12        if (grid[0][0] != 0) {
13            return false;
14        }
15      
16        int boardSize = grid.length;
17      
18        // Store the coordinates of each move number
19        // positions[moveNumber] = [row, column]
20        int[][] positions = new int[boardSize * boardSize][2];
21      
22        // Map each move number to its position on the board
23        for (int row = 0; row < boardSize; row++) {
24            for (int col = 0; col < boardSize; col++) {
25                int moveNumber = grid[row][col];
26                positions[moveNumber] = new int[] {row, col};
27            }
28        }
29      
30        // Verify that each consecutive move follows knight's movement rules
31        for (int moveNumber = 1; moveNumber < boardSize * boardSize; moveNumber++) {
32            int[] previousPosition = positions[moveNumber - 1];
33            int[] currentPosition = positions[moveNumber];
34          
35            // Calculate the absolute difference in rows and columns
36            int rowDifference = Math.abs(previousPosition[0] - currentPosition[0]);
37            int colDifference = Math.abs(previousPosition[1] - currentPosition[1]);
38          
39            // Knight moves in L-shape: 2 squares in one direction and 1 in perpendicular
40            boolean isValidKnightMove = (rowDifference == 1 && colDifference == 2) || 
41                                       (rowDifference == 2 && colDifference == 1);
42          
43            if (!isValidKnightMove) {
44                return false;
45            }
46        }
47      
48        return true;
49    }
50}
51
1class Solution {
2public:
3    bool checkValidGrid(vector<vector<int>>& grid) {
4        // Check if the knight starts at position (0, 0) with value 0
5        if (grid[0][0] != 0) {
6            return false;
7        }
8      
9        int n = grid.size();
10      
11        // Store the position (row, col) for each move number
12        // positions[i] represents the coordinate of the i-th move
13        vector<pair<int, int>> positions(n * n);
14      
15        // Build the positions array by iterating through the grid
16        for (int row = 0; row < n; ++row) {
17            for (int col = 0; col < n; ++col) {
18                int moveNumber = grid[row][col];
19                positions[moveNumber] = {row, col};
20            }
21        }
22      
23        // Validate that each consecutive move follows knight's movement rules
24        for (int moveNum = 1; moveNum < n * n; ++moveNum) {
25            // Get positions of previous and current moves
26            auto [prevRow, prevCol] = positions[moveNum - 1];
27            auto [currRow, currCol] = positions[moveNum];
28          
29            // Calculate the absolute differences in row and column
30            int rowDiff = abs(prevRow - currRow);
31            int colDiff = abs(prevCol - currCol);
32          
33            // Check if the move follows knight's L-shaped pattern:
34            // Either move 2 squares in one direction and 1 in the other,
35            // or move 1 square in one direction and 2 in the other
36            bool isValidKnightMove = (rowDiff == 1 && colDiff == 2) || 
37                                     (rowDiff == 2 && colDiff == 1);
38          
39            if (!isValidKnightMove) {
40                return false;
41            }
42        }
43      
44        return true;
45    }
46};
47
1/**
2 * Validates if a grid represents a valid knight's tour starting from position (0,0)
3 * A knight's tour is valid if:
4 * 1. It starts at position (0,0) with value 0
5 * 2. Each subsequent number (1, 2, 3, ..., n*n-1) is reachable from the previous number
6 *    by a valid knight's move (L-shaped: 2 squares in one direction, 1 square perpendicular)
7 * 
8 * @param grid - n x n grid containing numbers from 0 to n*n-1
9 * @returns true if the grid represents a valid knight's tour, false otherwise
10 */
11function checkValidGrid(grid: number[][]): boolean {
12    // Check if the knight's tour starts at the top-left corner (0,0)
13    if (grid[0][0] !== 0) {
14        return false;
15    }
16  
17    // Get the grid size
18    const gridSize: number = grid.length;
19  
20    // Create a position map to store the coordinates of each number
21    // positions[i] will store [row, column] where number i is located
22    const positions: number[][] = Array.from(
23        new Array(gridSize * gridSize), 
24        () => new Array(2).fill(0)
25    );
26  
27    // Populate the position map by scanning the entire grid
28    for (let row = 0; row < gridSize; ++row) {
29        for (let col = 0; col < gridSize; ++col) {
30            positions[grid[row][col]] = [row, col];
31        }
32    }
33  
34    // Verify that each consecutive pair of numbers forms a valid knight's move
35    for (let currentNumber = 1; currentNumber < gridSize * gridSize; ++currentNumber) {
36        // Get positions of the previous and current numbers
37        const previousPosition: number[] = positions[currentNumber - 1];
38        const currentPosition: number[] = positions[currentNumber];
39      
40        // Calculate the absolute differences in row and column
41        const rowDifference: number = Math.abs(previousPosition[0] - currentPosition[0]);
42        const colDifference: number = Math.abs(previousPosition[1] - currentPosition[1]);
43      
44        // Check if the move is a valid knight's move:
45        // Either (1 row, 2 cols) or (2 rows, 1 col)
46        const isValidKnightMove: boolean = 
47            (rowDifference === 1 && colDifference === 2) || 
48            (rowDifference === 2 && colDifference === 1);
49      
50        if (!isValidKnightMove) {
51            return false;
52        }
53    }
54  
55    return true;
56}
57

Time and Space Complexity

Time Complexity: O(nΒ²)

The algorithm consists of two main parts:

  1. Building the position array by iterating through the entire nΓ—n grid to record the position of each number from 0 to nΒ²-1. This requires visiting each cell once, resulting in O(nΒ²) operations.
  2. Iterating through the position array using pairwise to check if consecutive moves form valid knight moves. Since there are nΒ² positions, we perform nΒ²-1 comparisons, each taking O(1) time to calculate and verify the Manhattan distances.

Therefore, the overall time complexity is O(nΒ²) + O(nΒ²) = O(nΒ²).

Space Complexity: O(nΒ²)

The algorithm uses additional space for:

  1. The pos array which stores nΒ² position tuples, one for each number from 0 to nΒ²-1. This requires O(nΒ²) space.
  2. The pairwise iterator creates temporary variables for consecutive pairs, but only maintains O(1) space at any given time.
  3. A few constant space variables (dx, dy, ok, etc.).

The dominant space requirement comes from the pos array, making the overall space complexity O(nΒ²).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Off-by-One Error in Position Array Size

A common mistake is incorrectly sizing the position array. Since the grid contains values from 0 to n*n - 1, the position array must have exactly n*n elements. Creating an array that's too small will cause an IndexError when trying to store positions.

Incorrect:

positions = [None] * (n * n - 1)  # Wrong! Missing one element

Correct:

positions = [None] * (n * n)  # Correct size for values 0 to n*n-1

2. Using Wrong Iterator for Consecutive Pairs

When checking consecutive moves, developers might manually iterate with indices and make mistakes with boundary conditions or create unnecessarily complex code.

Error-prone approach:

for i in range(len(positions)):  # Wrong! Will cause IndexError
    current = positions[i]
    next = positions[i + 1]  # IndexError when i = len(positions) - 1

Better approach:

# Using explicit range limit
for i in range(len(positions) - 1):
    current = positions[i]
    next = positions[i + 1]

# Or using pairwise (Python 3.10+)
from itertools import pairwise
for current, next in pairwise(positions):
    # Process consecutive pairs

3. Incorrect Knight Move Validation Logic

A subtle but critical mistake is using OR instead of AND when checking the knight move pattern, or getting the movement pattern wrong entirely.

Incorrect validation:

# Wrong! This allows moves like (1,1) or (2,2)
is_valid = (row_diff == 1 or row_diff == 2) and (col_diff == 1 or col_diff == 2)

# Wrong! Using addition instead of checking the L-shape pattern
is_valid = row_diff + col_diff == 3

Correct validation:

# Must be exactly (1,2) or (2,1) pattern
is_valid = (row_diff == 1 and col_diff == 2) or (row_diff == 2 and col_diff == 1)

4. Forgetting to Check the Starting Position

It's easy to jump straight into validating moves without first checking if the knight starts at the correct position.

Missing initial check:

def checkValidGrid(self, grid):
    # Immediately building positions without checking start
    positions = [None] * (n * n)
    # ... rest of code

Complete solution:

def checkValidGrid(self, grid):
    if grid[0][0] != 0:  # Must check this first!
        return False
    # ... then proceed with validation

5. Not Handling Edge Cases

Failing to consider edge cases like a 1x1 grid where there's only one cell and no moves to validate.

Robust handling:

if n == 1:
    return grid[0][0] == 0  # Only need to check if single cell has value 0
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What's the relationship between a tree and a graph?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More