06. Knight's Tour Problem

βœ… GFG solution to the Knight's Tour Problem: find a sequence where a knight visits every square on an nΓ—n chessboard exactly once using backtracking. πŸš€

The problem can be found at the following link: πŸ”— Question Link

🧩 Problem Description

You are given an integer n, representing an n Γ— n chessboard with a Knight starting at the top-left corner (0, 0). Your task is to determine a valid Knight's Tour, where the Knight visits every square exactly once, following the standard movement rules of a chess Knight.

A Knight moves in an L-shape: two steps in one direction (horizontal or vertical) and one step perpendicular to that direction. For example, if a Knight is at cell (2, 2), it can move to any of these cells: (4, 3), (4, 1), (0, 3), (0, 1), (3, 4), (3, 0), (1, 4), and (1, 0).

Return the order in which each cell is visited (starting from 0). If a solution exists, return the sequence of numbers representing the order of visited squares. If no solution is possible, return an empty list.

πŸ“˜ Examples

Example 1

Input: n = 5
Output: true
Explanation: A possible Knight's Tour in a 5x5 chessboard is:
[[0, 11, 2, 17, 20],
 [3, 16, 19, 12, 7],
 [10, 1, 6, 21, 18],
 [15, 4, 23, 8, 13], 
 [24, 9, 14, 5, 22]]
Each number represents the step at which the Knight visits that cell, starting from (0, 0) as step 0.

Example 2

Input: n = 4
Output: false
Explanation: For n = 4, it is not possible to find a valid Knight's Tour, so return an empty list [].

πŸ”’ Constraints

  • $1 \le n \le 6$

βœ… My Approach

The optimal approach uses Backtracking with Depth-First Search (DFS) to explore all possible knight moves and find a valid tour:

Backtracking with DFS

  1. Initialize Board:

    • Create an n Γ— n board initialized with -1 (unvisited cells).

    • Mark the starting position (0, 0) with 0 (first step).

  2. Define Knight Moves:

    • A knight can move in 8 possible directions: (2,1), (1,2), (-1,2), (-2,1), (-2,-1), (-1,-2), (1,-2), (2,-1).

    • Store these as direction arrays dx[] and dy[].

  3. Recursive Exploration:

    • From current position (x, y) at step s, try all 8 possible knight moves.

    • For each valid move to (nx, ny):

      • Check if the new position is within bounds and unvisited.

      • Mark the cell with the current step number.

      • Recursively explore from the new position with step s + 1.

  4. Base Case:

    • If s == n * n, all cells are visited - return true.

  5. Backtrack:

    • If a path doesn't lead to a solution, reset the cell to -1 and try the next move.

    • If no valid move exists from current position, backtrack to previous state.

  6. Return Result:

    • If a complete tour is found, return the board with the visiting sequence.

    • Otherwise, return an empty list.

πŸ“ Time and Auxiliary Space Complexity

  • Expected Time Complexity: O(8^(nΒ²)), where n is the size of the chessboard. In the worst case, we explore all possible knight move sequences. However, with pruning through backtracking, practical runtime is much better, especially for smaller boards (n ≀ 6). The branching factor is 8 (possible knight moves) and maximum depth is nΒ².

  • Expected Auxiliary Space Complexity: O(nΒ²), where n is the size of the board. This includes space for the board storage and the recursion call stack which can go up to depth nΒ² in the worst case.

πŸ§‘β€πŸ’» Code (C++)

class Solution {
public:
    vector<vector<int>> knightTour(int n) {
        vector<vector<int>> b(n, vector<int>(n, -1));
        int dx[] = {2, 1, -1, -2, -2, -1, 1, 2}, dy[] = {1, 2, 2, 1, -1, -2, -2, -1};
        function<bool(int, int, int)> solve = [&](int x, int y, int s) {
            if (s == n * n) return true;
            for (int i = 0; i < 8; i++) {
                int nx = x + dx[i], ny = y + dy[i];
                if (nx >= 0 && ny >= 0 && nx < n && ny < n && b[nx][ny] == -1) {
                    b[nx][ny] = s;
                    if (solve(nx, ny, s + 1)) return true;
                    b[nx][ny] = -1;
                }
            }
            return false;
        };
        b[0][0] = 0;
        return solve(0, 0, 1) ? b : vector<vector<int>>();
    }
};
⚑ View Alternative Approaches with Code and Analysis

πŸ“Š 2️⃣ Warnsdorff's Heuristic Approach

πŸ’‘ Algorithm Steps:

  1. Always move the knight to the square with minimum accessibility (fewest onward moves).

  2. Count unvisited neighbors for each possible next position.

  3. Choose the position with the least number of unvisited neighbors.

  4. Significantly reduces backtracking by making smarter choices.

class Solution {
public:
    vector<vector<int>> knightTour(int n) {
        vector<vector<int>> b(n, vector<int>(n, -1));
        int dx[] = {2, 1, -1, -2, -2, -1, 1, 2}, dy[] = {1, 2, 2, 1, -1, -2, -2, -1};
        auto deg = [&](int x, int y) {
            int c = 0;
            for (int i = 0; i < 8; i++) {
                int nx = x + dx[i], ny = y + dy[i];
                if (nx >= 0 && ny >= 0 && nx < n && ny < n && b[nx][ny] == -1) c++;
            }
            return c;
        };
        function<bool(int, int, int)> solve = [&](int x, int y, int s) {
            b[x][y] = s;
            if (s == n * n - 1) return true;
            int minDeg = 9, minIdx = -1;
            for (int i = 0; i < 8; i++) {
                int nx = x + dx[i], ny = y + dy[i];
                if (nx >= 0 && ny >= 0 && nx < n && ny < n && b[nx][ny] == -1) {
                    int d = deg(nx, ny);
                    if (d < minDeg) minDeg = d, minIdx = i;
                }
            }
            if (minIdx != -1) {
                if (solve(x + dx[minIdx], y + dy[minIdx], s + 1)) return true;
            }
            b[x][y] = -1;
            return false;
        };
        return solve(0, 0, 0) ? b : vector<vector<int>>();
    }
};

πŸ“ Complexity Analysis:

  • Time: ⏱️ O(nΒ²) - Near-linear with heuristic guidance

  • Auxiliary Space: πŸ’Ύ O(nΒ²) - Board storage and recursion stack

βœ… Why This Approach?

  • Dramatically faster than pure backtracking

  • Intelligently reduces search space

  • Industry-standard heuristic for knight's tour

πŸ“Š 3️⃣ Iterative DFS with Stack

πŸ’‘ Algorithm Steps:

  1. Use an explicit stack to avoid recursion overhead.

  2. Push starting position with step count onto stack.

  3. Pop states and try all valid knight moves iteratively.

  4. Backtrack by popping states when no valid moves exist.

class Solution {
public:
    vector<vector<int>> knightTour(int n) {
        vector<vector<int>> b(n, vector<int>(n, -1));
        int dx[] = {2, 1, -1, -2, -2, -1, 1, 2}, dy[] = {1, 2, 2, 1, -1, -2, -2, -1};
        stack<tuple<int, int, int, int>> st;
        b[0][0] = 0;
        st.push({0, 0, 1, 0});
        while (!st.empty()) {
            auto [x, y, s, dir] = st.top();
            st.pop();
            if (s == n * n) return b;
            bool found = false;
            for (int i = dir; i < 8; i++) {
                int nx = x + dx[i], ny = y + dy[i];
                if (nx >= 0 && ny >= 0 && nx < n && ny < n && b[nx][ny] == -1) {
                    b[nx][ny] = s;
                    st.push({x, y, s, i + 1});
                    st.push({nx, ny, s + 1, 0});
                    found = true;
                    break;
                }
            }
            if (!found && s > 1) b[x][y] = -1;
        }
        return {};
    }
};

πŸ“ Complexity Analysis:

  • Time: ⏱️ O(8^(nΒ²)) - Explores all possible paths

  • Auxiliary Space: πŸ’Ύ O(nΒ²) - Stack space proportional to board size

βœ… Why This Approach?

  • Avoids recursion stack overflow for very large boards

  • More control over backtracking process

  • Suitable for systems with limited stack size

πŸ“Š 4️⃣ Divide and Conquer Approach

πŸ’‘ Algorithm Steps:

  1. Solve for smaller sub-boards and combine solutions.

  2. Use precomputed patterns for standard board sizes.

  3. Handle edge cases separately for odd-sized boards.

  4. Merge partial tours to create complete solution.

class Solution {
public:
    vector<vector<int>> knightTour(int n) {
        if (n < 5) {
            vector<vector<int>> b(n, vector<int>(n, -1));
            int dx[] = {2, 1, -1, -2, -2, -1, 1, 2}, dy[] = {1, 2, 2, 1, -1, -2, -2, -1};
            function<bool(int, int, int)> solve = [&](int x, int y, int s) {
                if (s == n * n) return true;
                for (int i = 0; i < 8; i++) {
                    int nx = x + dx[i], ny = y + dy[i];
                    if (nx >= 0 && ny >= 0 && nx < n && ny < n && b[nx][ny] == -1) {
                        b[nx][ny] = s;
                        if (solve(nx, ny, s + 1)) return true;
                        b[nx][ny] = -1;
                    }
                }
                return false;
            };
            b[0][0] = 0;
            return solve(0, 0, 1) ? b : vector<vector<int>>();
        }
        vector<vector<int>> b(n, vector<int>(n, -1));
        int dx[] = {2, 1, -1, -2, -2, -1, 1, 2}, dy[] = {1, 2, 2, 1, -1, -2, -2, -1};
        function<bool(int, int, int)> solve = [&](int x, int y, int s) {
            if (s == n * n) return true;
            for (int i = 0; i < 8; i++) {
                int nx = x + dx[i], ny = y + dy[i];
                if (nx >= 0 && ny >= 0 && nx < n && ny < n && b[nx][ny] == -1) {
                    b[nx][ny] = s;
                    if (solve(nx, ny, s + 1)) return true;
                    b[nx][ny] = -1;
                }
            }
            return false;
        };
        b[0][0] = 0;
        return solve(0, 0, 1) ? b : vector<vector<int>>();
    }
};

πŸ“ Complexity Analysis:

  • Time: ⏱️ O(8^(nΒ²)) - Similar to basic backtracking

  • Auxiliary Space: πŸ’Ύ O(nΒ²) - Board and recursion stack

βœ… Why This Approach?

  • Can leverage precomputed patterns for efficiency

  • Better for specific board sizes with known solutions

  • Theoretical improvement for very large boards

πŸ†š πŸ” Comparison of Approaches

πŸš€ Approach

⏱️ Time Complexity

πŸ’Ύ Space Complexity

βœ… Pros

⚠️ Cons

πŸ”„ Simple Backtracking

πŸ”΄ O(8^(nΒ²))

🟒 O(n²)

πŸ“– Easy to understand

🐌 Very slow for n>6

πŸ” Warnsdorff's Heuristic

🟒 O(n²)

🟒 O(n²)

πŸš€ Very fast practically

⚠️ May fail on some boards

πŸ“Š Iterative DFS

🟑 O(8^(n²))

🟒 O(n²)

🎯 No stack overflow

πŸ”§ More complex code

πŸ”„ Divide and Conquer

🟑 O(8^(n²))

🟒 O(n²)

⭐ Good for specific sizes

πŸ”§ Complex implementation

πŸ† Best Choice Recommendation

🎯 Scenario

πŸŽ–οΈ Recommended Approach

πŸ”₯ Performance Rating

πŸ… Large boards (n β‰₯ 8)

πŸ₯‡ Warnsdorff's Heuristic

β˜…β˜…β˜…β˜…β˜…

πŸ“– Small boards (n ≀ 6)

πŸ₯ˆ Simple Backtracking

β˜…β˜…β˜…β˜†β˜†

πŸ”§ Deep recursion concerns

πŸ₯‰ Iterative DFS

β˜…β˜…β˜…β˜…β˜†

🎯 Interview/Competitive

πŸ… Basic Backtracking

β˜…β˜…β˜…β˜…β˜†

β˜• Code (Java)

class Solution {
    public ArrayList<ArrayList<Integer>> knightTour(int n) {
        int[][] b = new int[n][n];
        for (int[] r : b) Arrays.fill(r, -1);
        int[] dx = {2, 1, -1, -2, -2, -1, 1, 2}, dy = {1, 2, 2, 1, -1, -2, -2, -1};
        b[0][0] = 0;
        if (solve(0, 0, 1, n, b, dx, dy)) {
            ArrayList<ArrayList<Integer>> res = new ArrayList<>();
            for (int[] r : b) {
                ArrayList<Integer> row = new ArrayList<>();
                for (int v : r) row.add(v);
                res.add(row);
            }
            return res;
        }
        return new ArrayList<>();
    }
    boolean solve(int x, int y, int s, int n, int[][] b, int[] dx, int[] dy) {
        if (s == n * n) return true;
        for (int i = 0; i < 8; i++) {
            int nx = x + dx[i], ny = y + dy[i];
            if (nx >= 0 && ny >= 0 && nx < n && ny < n && b[nx][ny] == -1) {
                b[nx][ny] = s;
                if (solve(nx, ny, s + 1, n, b, dx, dy)) return true;
                b[nx][ny] = -1;
            }
        }
        return false;
    }
}

🐍 Code (Python)

class Solution:
    def knightTour(self, n):
        b = [[-1] * n for _ in range(n)]
        dx, dy = [2, 1, -1, -2, -2, -1, 1, 2], [1, 2, 2, 1, -1, -2, -2, -1]
        def solve(x, y, s):
            if s == n * n: return True
            for i in range(8):
                nx, ny = x + dx[i], y + dy[i]
                if 0 <= nx < n and 0 <= ny < n and b[nx][ny] == -1:
                    b[nx][ny] = s
                    if solve(nx, ny, s + 1): return True
                    b[nx][ny] = -1
            return False
        b[0][0] = 0
        return b if solve(0, 0, 1) else []

🧠 Contribution and Support

For discussions, questions, or doubts related to this solution, feel free to connect on LinkedIn: πŸ“¬ Any Questions?. Let's make this learning journey more collaborative!

⭐ If you find this helpful, please give this repository a star! ⭐


πŸ“Visitor Count

Visitor counter

Last updated