Start learning free

LESSON 266 OF 348 · BACKTRACKING

N-Queens in TypeScript: Backtracking Row by Row

Practice O(n!) Read the full explanation

The n-queens puzzle is the problem of placing n queens on an n x n chessboard such that no two queens attack each other.

Given an integer n, return all distinct solutions to the n-queens puzzle. You may return the answer in any order.

Each solution contains a distinct board configuration of the n-queens' placement, and is represented as an array containing the column for each row of the matrix.

 



Example:

Input: n = 4
Output: [[1, 3, 0, 2], [2, 0, 3, 1]]
Explanation: There exist two distinct solutions to the 4-queens puzzle as shown above

 

Constraints:

  • 1 <= n <= 9


STEP 1 OF 14
    Ctrl + Enter to check

    TYPESCRIPT · BACKTRACKING

    N Queens in TypeScript, explained

    Problem overview

    The method solveNQueens receives an integer n and must place n queens on an n x n chessboard so that no two queens attack each other. A queen attacks along its row, its column and both diagonals. The method returns every distinct solution, in any order.

    A solution is an array of length n in which element r is the column of the queen in row r. For n = 4 there are two solutions: [1, 3, 0, 2] and [2, 0, 3, 1]. In the first, the queens are at row 0 column 1, row 1 column 3, row 2 column 0 and row 3 column 2. The statement's limits are 1 <= n <= 9.

    Approach: one queen per row, undo when stuck

    Every row must hold exactly one queen: two in a row would attack each other, and with n rows and n queens none can be left over. That turns the puzzle into a sequence of choices. For row 0 choose a column, for row 1 choose a column, and so on. Trying all n to the power n combinations is wasteful, since most are wrong from the first few rows.

    Backtracking prunes them early. Build the answer one row at a time, and put a queen in a square only if it is safe from the queens already placed. If a row has no safe square, return to the previous row and try its next column. When all n rows are filled, copy the placement into the results.

    The pattern for each choice is always the same: add it, recurse, remove it. Removing the choice after the recursive call returns leaves the shared array exactly as it was, ready for the next column.

    Checking whether a square is safe needs only the columns of the earlier rows. Rows cannot clash, because each row gets one queen. A new queen at (row, col) clashes with a queen at (i, placement[i]) in two cases:

    • they share a column: placement[i] == col;
    • they share a diagonal: the row distance equals the column distance, Math.abs(row - i) == Math.abs(col - placement[i]).

    Step by step

    1. Create the results list, placements, of type number[][], empty at first.
    2. Start the search with this.backtracking(n, 0, [], placements): row 0, with nothing placed.
    3. Return placements.
    4. Declare backtracking with n, row, placement and placements. Each call handles one row, and placement holds the columns chosen for the rows above it.
    5. Stop when the board is full. If row == n, push a copy, [...placement], into placements, and return. The copy matters, as explained below.
    6. Loop over the columns col from 0 to n - 1.
    7. Test the square with this.isValid(placement, row, col).
    8. Place the queen by pushing col onto placement.
    9. Recurse into the next row with this.backtracking(n, row + 1, placement, placements).
    10. Undo the choice with placement.pop().
    11. Write isValid to return a boolean, true when the square is safe.
    12. Scan the earlier rows i from 0 to row - 1. The queen of row i sits at placement[i].
    13. Return false on a clash: same column, or equal row and column distances.
    14. Return true if the loop finds no conflict.

    The solution in TypeScript

    class Solution {
        isValid(placement: number[], row: number, col: number): boolean {
            for (let i = 0; i < row; i++) {
                if (placement[i] == col ||
                        Math.abs(row - i) == Math.abs(col - placement[i])) {
                    return false;
                }
            }
            return true;
        }
    
        backtracking(n: number, row: number, placement: number[], placements: number[][]) {
            if (row == n) {
                placements.push([...placement]);
                return;
            }
            for (let col = 0; col < n; col++) {
                if (this.isValid(placement, row, col)) {
                    placement.push(col);
                    this.backtracking(n, row + 1, placement, placements);
                    placement.pop();
                }
            }
        }
    
        solveNQueens(n: number): number[][] {
            let placements: number[][] = [];
            this.backtracking(n, 0, [], placements);
            return placements;
        }
    }
    

    TypeScript details worth knowing:

    • Why copy with [...placement]. placement is one array that every call shares and keeps changing. Pushing it itself into placements would store a reference to that array, and by the end of the search it is empty again, so every stored solution would show up as []. The spread syntax creates a new array with the current elements.
    • Arrays as a stack. push adds at the end and pop removes from the end, which is the add-and-undo pattern backtracking needs.
    • No return type on backtracking. The method returns nothing, so TypeScript infers void. Writing : void is allowed and is clearer.
    • Parameters are shared by reference. Arrays are objects, so changes made inside a recursive call are visible to the caller.
    • == on numbers behaves the same as === here.

    Complexity

    Time: exponential. There are at most n choices in the first row and fewer in the later ones, since each queen rules out squares. The exact count has no simple formula, but it is far below n^n, and it still grows quickly: the numbers of solutions for n = 1 to 9 are 1, 0, 0, 2, 10, 4, 40, 92, 352. That is why the lesson stops at n = 9. Each isValid call costs O(n).

    Space: the recursion and the placement array are O(n) deep. The result itself holds one array of n numbers per solution, which dominates for larger n.

    Edge cases and common mistakes

    • n = 1. One queen on a one-square board, so the result is [[0]].
    • n = 2 and n = 3. No arrangement works, so the result is [].
    • Forgetting pop. The queen from the failed attempt stays in placement, and later rows are checked against queens that are no longer there.
    • Pushing placement without copying, as described above.
    • Checking only one diagonal. The test compares absolute distances so that both directions are covered. Without Math.abs, only one diagonal direction is caught.
    • Checking the current row. The scan stops at row - 1. The current row has no queen yet.
    • Result order. The loop tries columns from left to right, so the solutions come out in increasing order: for n = 4, [1, 3, 0, 2] and then [2, 0, 3, 1]. Any order is accepted.

    Testing

    • n = 1 gives [[0]].
    • n = 2 and n = 3 give [].
    • n = 4 gives [[1, 3, 0, 2], [2, 0, 3, 1]].
    • n = 6 gives 4 solutions, starting with [1, 3, 5, 0, 2, 4].
    • n = 8 gives 92 solutions, and n = 9 gives 352.
    Keep going with TypeScriptAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free