Knight's Tour
Algorithm · Stretch
Step through it: Backtracking pattern
holding [ ]
Step 1 of 7
Goal: list every subset of [1, 2]. At each number we choose: take it or skip it. All the choices form a tree of decisions. Start with nothing chosen.
Step 2 of 7
Choose to take 1.
Step 3 of 7
Take 2 as well. No numbers are left, so write this subset down.
Step 4 of 7
Undo that last choice (the "back" in backtracking) and skip 2 instead. Write [1] down.
Step 5 of 7
Undo taking 1 and skip it instead: holding [ ] again.
Step 6 of 7
From [ ], take or skip 2 the same way: [2] and [ ]. The four leaves are every subset. The recipe is choose, explore, undo. If a choice breaks a rule, stop that branch early (pruning).
Step 7 of 7
Your turn.
A knight's tour is a sequence of moves of a knight on a chessboard such that the knight visits every square only once. If the knight ends on a square that is one knight's move from the beginning square (so that it could tour the board again immediately, following the same path), the tour is closed, otherwise it is open.
The knight's tour problem is the mathematical problem of
finding a knight's tour. Creating a program to find a knight's
tour is a common problem given to computer science students.
Variations of the knight's tour problem involve chessboards of
different sizes than the usual 8×8, as well as irregular
(non-rectangular) boards.
The knight's tour problem is an instance of the more general Hamiltonian path problem in graph theory. The problem of finding a closed knight's tour is similarly an instance of the Hamiltonian cycle problem.
An open knight's tour of a chessboard.
An animation of an open knight's tour on a 5 by 5 board.
Code
/**
* @param {number[][]} chessboard
* @param {number[]} position
* @return {number[][]}
*/
function getPossibleMoves(chessboard, position) {
// Generate all knight moves (even those that go beyond the board).
const possibleMoves = [
[position[0] - 1, position[1] - 2],
[position[0] - 2, position[1] - 1],
[position[0] + 1, position[1] - 2],
[position[0] + 2, position[1] - 1],
[position[0] - 2, position[1] + 1],
[position[0] - 1, position[1] + 2],
[position[0] + 1, position[1] + 2],
[position[0] + 2, position[1] + 1],
];
// Filter out all moves that go beyond the board.
return possibleMoves.filter((move) => {
const boardSize = chessboard.length;
return move[0] >= 0 && move[1] >= 0 && move[0] < boardSize && move[1] < boardSize;
});
}
/**
* @param {number[][]} chessboard
* @param {number[]} move
* @return {boolean}
*/
function isMoveAllowed(chessboard, move) {
return chessboard[move[0]][move[1]] !== 1;
}
/**
* @param {number[][]} chessboard
* @param {number[][]} moves
* @return {boolean}
*/
function isBoardCompletelyVisited(chessboard, moves) {
const totalPossibleMovesCount = chessboard.length ** 2;
const existingMovesCount = moves.length;
return totalPossibleMovesCount === existingMovesCount;
}
/**
* @param {number[][]} chessboard
* @param {number[][]} moves
* @return {boolean}
*/
function knightTourRecursive(chessboard, moves) {
const currentChessboard = chessboard;
// If board has been completely visited then we've found a solution.
if (isBoardCompletelyVisited(currentChessboard, moves)) {
return true;
}
// Get next possible knight moves.
const lastMove = moves[moves.length - 1];
const possibleMoves = getPossibleMoves(currentChessboard, lastMove);
// Try to do next possible moves.
for (let moveIndex = 0; moveIndex < possibleMoves.length; moveIndex += 1) {
const currentMove = possibleMoves[moveIndex];
// Check if current move is allowed. We aren't allowed to go to
// the same cells twice.
if (isMoveAllowed(currentChessboard, currentMove)) {
// Actually do the move.
moves.push(currentMove);
currentChessboard[currentMove[0]][currentMove[1]] = 1;
// If further moves starting from current are successful then
// return true meaning the solution is found.
if (knightTourRecursive(currentChessboard, moves)) {
return true;
}
// BACKTRACKING.
// If current move was unsuccessful then step back and try to do another move.
moves.pop();
currentChessboard[currentMove[0]][currentMove[1]] = 0;
}
}
// Return false if we haven't found solution.
return false;
}
/**
* @param {number} chessboardSize
* @return {number[][]}
*/
export default function knightTour(chessboardSize) {
// Init chessboard.
const chessboard = Array(chessboardSize).fill(null).map(() => Array(chessboardSize).fill(0));
// Init moves array.
const moves = [];
// Do first move and place the knight to the 0x0 cell.
const firstMove = [0, 0];
moves.push(firstMove);
chessboard[firstMove[0]][firstMove[0]] = 1;
// Recursively try to do the next move.
const solutionWasFound = knightTourRecursive(chessboard, moves);
return solutionWasFound ? moves : [];
}References
Practice
Backtracking
- Binary Tree PathsEasy
- SubsetsMedium
- Subsets IIMedium
- PermutationsMedium
- Permutations IIMedium
- CombinationsMedium
- Combination SumMedium
- Combination Sum IIMedium
- Combination Sum IIIMedium
- Letter Combinations of a Phone NumberMedium
- Generate ParenthesesMedium
- Palindrome PartitioningMedium
- Word SearchMedium
- Restore IP AddressesMedium
- Letter Case PermutationMedium
- Partition to K Equal Sum SubsetsMedium
- N-QueensHard
- Sudoku SolverHard
- Word Break IIHard
- Expression Add OperatorsHard
Spotted a mistake or something unclear? Report it on GitHub.