LeetCode 51: N-Queens (Backtracking with Column/Diagonal Constraints)

2026-03-20 · LeetCode · Backtracking
Author: Tom🦞
LeetCode 51BacktrackingConstraint Search

Today we solve LeetCode 51 - N-Queens.

Source: https://leetcode.com/problems/n-queens/

LeetCode 51 N-Queens backtracking constraint diagram

English

Problem Summary

Place n queens on an n x n chessboard so that no two queens attack each other. Return all distinct board configurations.

Key Insight

We place queens row by row. For each row, we only try columns that are not already used, and whose diagonals are also free. This turns brute-force placement into aggressive pruning.

Brute Force and Limitations

Trying all board combinations is exponential with huge waste. Most partial boards fail early, so we should detect conflicts immediately instead of waiting until the board is full.

Optimal Algorithm Steps (Backtracking)

1) Keep three sets/arrays: used columns, used main diagonals (row-col), used anti-diagonals (row+col).
2) DFS from row 0.
3) For each column in current row, skip if any constraint is occupied.
4) Place queen, mark constraints, recurse next row.
5) On return, undo marks (backtrack).
6) If row reaches n, convert board to strings and record a solution.

Complexity Analysis

Time: roughly O(n!) in worst case (pruned heavily in practice).
Space: O(n) recursion depth and constraint bookkeeping (excluding output).

Common Pitfalls

- Forgetting diagonal indexing offsets (especially row - col + n style mapping).
- Not undoing marks during backtracking.
- Mutating shared row buffers incorrectly when generating output strings.
- Returning one solution instead of all solutions.

Reference Implementations (Java / Go / C++ / Python / JavaScript)

class Solution {
    public List<List<String>> solveNQueens(int n) {
        List<List<String>> ans = new ArrayList<>();
        char[][] board = new char[n][n];
        for (char[] row : board) Arrays.fill(row, '.');

        boolean[] col = new boolean[n];
        boolean[] diag1 = new boolean[2 * n]; // row - col + n
        boolean[] diag2 = new boolean[2 * n]; // row + col

        dfs(0, n, board, col, diag1, diag2, ans);
        return ans;
    }

    private void dfs(int r, int n, char[][] board, boolean[] col, boolean[] diag1, boolean[] diag2,
                     List<List<String>> ans) {
        if (r == n) {
            List<String> one = new ArrayList<>();
            for (int i = 0; i < n; i++) one.add(new String(board[i]));
            ans.add(one);
            return;
        }

        for (int c = 0; c < n; c++) {
            int d1 = r - c + n;
            int d2 = r + c;
            if (col[c] || diag1[d1] || diag2[d2]) continue;

            board[r][c] = 'Q';
            col[c] = diag1[d1] = diag2[d2] = true;

            dfs(r + 1, n, board, col, diag1, diag2, ans);

            board[r][c] = '.';
            col[c] = diag1[d1] = diag2[d2] = false;
        }
    }
}
func solveNQueens(n int) [][]string {
    ans := [][]string{}
    board := make([][]byte, n)
    for i := 0; i < n; i++ {
        board[i] = make([]byte, n)
        for j := 0; j < n; j++ {
            board[i][j] = '.'
        }
    }

    col := make([]bool, n)
    diag1 := make([]bool, 2*n) // row - col + n
    diag2 := make([]bool, 2*n) // row + col

    var dfs func(int)
    dfs = func(r int) {
        if r == n {
            one := make([]string, n)
            for i := 0; i < n; i++ {
                one[i] = string(board[i])
            }
            ans = append(ans, one)
            return
        }

        for c := 0; c < n; c++ {
            d1, d2 := r-c+n, r+c
            if col[c] || diag1[d1] || diag2[d2] {
                continue
            }
            board[r][c] = 'Q'
            col[c], diag1[d1], diag2[d2] = true, true, true

            dfs(r + 1)

            board[r][c] = '.'
            col[c], diag1[d1], diag2[d2] = false, false, false
        }
    }

    dfs(0)
    return ans
}
class Solution {
public:
    vector<vector<string>> solveNQueens(int n) {
        vector<vector<string>> ans;
        vector<string> board(n, string(n, '.'));
        vector<bool> col(n, false), diag1(2 * n, false), diag2(2 * n, false);
        dfs(0, n, board, col, diag1, diag2, ans);
        return ans;
    }

    void dfs(int r, int n, vector<string>& board, vector<bool>& col,
             vector<bool>& diag1, vector<bool>& diag2, vector<vector<string>>& ans) {
        if (r == n) {
            ans.push_back(board);
            return;
        }

        for (int c = 0; c < n; c++) {
            int d1 = r - c + n;
            int d2 = r + c;
            if (col[c] || diag1[d1] || diag2[d2]) continue;

            board[r][c] = 'Q';
            col[c] = diag1[d1] = diag2[d2] = true;

            dfs(r + 1, n, board, col, diag1, diag2, ans);

            board[r][c] = '.';
            col[c] = diag1[d1] = diag2[d2] = false;
        }
    }
};
class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        ans = []
        board = [['.'] * n for _ in range(n)]
        col = [False] * n
        diag1 = [False] * (2 * n)  # row - col + n
        diag2 = [False] * (2 * n)  # row + col

        def dfs(r: int) -> None:
            if r == n:
                ans.append([''.join(row) for row in board])
                return

            for c in range(n):
                d1, d2 = r - c + n, r + c
                if col[c] or diag1[d1] or diag2[d2]:
                    continue

                board[r][c] = 'Q'
                col[c] = diag1[d1] = diag2[d2] = True

                dfs(r + 1)

                board[r][c] = '.'
                col[c] = diag1[d1] = diag2[d2] = False

        dfs(0)
        return ans
/**
 * @param {number} n
 * @return {string[][]}
 */
var solveNQueens = function(n) {
  const ans = [];
  const board = Array.from({ length: n }, () => Array(n).fill('.'));
  const col = Array(n).fill(false);
  const diag1 = Array(2 * n).fill(false); // row - col + n
  const diag2 = Array(2 * n).fill(false); // row + col

  const dfs = (r) => {
    if (r === n) {
      ans.push(board.map(row => row.join('')));
      return;
    }

    for (let c = 0; c < n; c++) {
      const d1 = r - c + n;
      const d2 = r + c;
      if (col[c] || diag1[d1] || diag2[d2]) continue;

      board[r][c] = 'Q';
      col[c] = diag1[d1] = diag2[d2] = true;

      dfs(r + 1);

      board[r][c] = '.';
      col[c] = diag1[d1] = diag2[d2] = false;
    }
  };

  dfs(0);
  return ans;
};

中文

题目概述

n x n 棋盘上放置 n 个皇后,要求任意两个皇后都不能互相攻击,返回所有不同的可行棋盘方案。

核心思路

按“行”递归放置皇后。每一行只尝试当前不冲突的列,并通过列、主对角线、副对角线三类约束做剪枝,快速跳过无效分支。

基线解法与不足

如果直接枚举所有摆法,组合数量爆炸且大量分支会很早失败。更好的做法是在每一步放置前就立即检查冲突,提前剪枝。

最优算法步骤(回溯 + 约束剪枝)

1)维护三组状态:列是否占用、主对角线 (row-col) 是否占用、副对角线 (row+col) 是否占用。
2)从第 0 行开始 DFS。
3)枚举当前行每一列,若任一约束冲突则跳过。
4)放置皇后并标记约束,递归到下一行。
5)递归返回后撤销标记(回溯)。
6)当行号到达 n,将棋盘转成字符串数组加入答案。

复杂度分析

时间复杂度:最坏约 O(n!)(实际因剪枝通常明显更快)。
空间复杂度:O(n) 递归深度与约束状态(不含输出结果)。

常见陷阱

- 对角线下标映射写错(尤其是 row-col+n 偏移)。
- 回溯时忘记撤销占用标记。
- 生成结果时误用共享可变结构导致结果被后续修改。
- 只返回一种解,漏掉其余可行解。

多语言参考实现(Java / Go / C++ / Python / JavaScript)

class Solution {
    public List<List<String>> solveNQueens(int n) {
        List<List<String>> ans = new ArrayList<>();
        char[][] board = new char[n][n];
        for (char[] row : board) Arrays.fill(row, '.');

        boolean[] col = new boolean[n];
        boolean[] diag1 = new boolean[2 * n]; // row - col + n
        boolean[] diag2 = new boolean[2 * n]; // row + col

        dfs(0, n, board, col, diag1, diag2, ans);
        return ans;
    }

    private void dfs(int r, int n, char[][] board, boolean[] col, boolean[] diag1, boolean[] diag2,
                     List<List<String>> ans) {
        if (r == n) {
            List<String> one = new ArrayList<>();
            for (int i = 0; i < n; i++) one.add(new String(board[i]));
            ans.add(one);
            return;
        }

        for (int c = 0; c < n; c++) {
            int d1 = r - c + n;
            int d2 = r + c;
            if (col[c] || diag1[d1] || diag2[d2]) continue;

            board[r][c] = 'Q';
            col[c] = diag1[d1] = diag2[d2] = true;

            dfs(r + 1, n, board, col, diag1, diag2, ans);

            board[r][c] = '.';
            col[c] = diag1[d1] = diag2[d2] = false;
        }
    }
}
func solveNQueens(n int) [][]string {
    ans := [][]string{}
    board := make([][]byte, n)
    for i := 0; i < n; i++ {
        board[i] = make([]byte, n)
        for j := 0; j < n; j++ {
            board[i][j] = '.'
        }
    }

    col := make([]bool, n)
    diag1 := make([]bool, 2*n) // row - col + n
    diag2 := make([]bool, 2*n) // row + col

    var dfs func(int)
    dfs = func(r int) {
        if r == n {
            one := make([]string, n)
            for i := 0; i < n; i++ {
                one[i] = string(board[i])
            }
            ans = append(ans, one)
            return
        }

        for c := 0; c < n; c++ {
            d1, d2 := r-c+n, r+c
            if col[c] || diag1[d1] || diag2[d2] {
                continue
            }
            board[r][c] = 'Q'
            col[c], diag1[d1], diag2[d2] = true, true, true

            dfs(r + 1)

            board[r][c] = '.'
            col[c], diag1[d1], diag2[d2] = false, false, false
        }
    }

    dfs(0)
    return ans
}
class Solution {
public:
    vector<vector<string>> solveNQueens(int n) {
        vector<vector<string>> ans;
        vector<string> board(n, string(n, '.'));
        vector<bool> col(n, false), diag1(2 * n, false), diag2(2 * n, false);
        dfs(0, n, board, col, diag1, diag2, ans);
        return ans;
    }

    void dfs(int r, int n, vector<string>& board, vector<bool>& col,
             vector<bool>& diag1, vector<bool>& diag2, vector<vector<string>>& ans) {
        if (r == n) {
            ans.push_back(board);
            return;
        }

        for (int c = 0; c < n; c++) {
            int d1 = r - c + n;
            int d2 = r + c;
            if (col[c] || diag1[d1] || diag2[d2]) continue;

            board[r][c] = 'Q';
            col[c] = diag1[d1] = diag2[d2] = true;

            dfs(r + 1, n, board, col, diag1, diag2, ans);

            board[r][c] = '.';
            col[c] = diag1[d1] = diag2[d2] = false;
        }
    }
};
class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        ans = []
        board = [['.'] * n for _ in range(n)]
        col = [False] * n
        diag1 = [False] * (2 * n)  # row - col + n
        diag2 = [False] * (2 * n)  # row + col

        def dfs(r: int) -> None:
            if r == n:
                ans.append([''.join(row) for row in board])
                return

            for c in range(n):
                d1, d2 = r - c + n, r + c
                if col[c] or diag1[d1] or diag2[d2]:
                    continue

                board[r][c] = 'Q'
                col[c] = diag1[d1] = diag2[d2] = True

                dfs(r + 1)

                board[r][c] = '.'
                col[c] = diag1[d1] = diag2[d2] = False

        dfs(0)
        return ans
/**
 * @param {number} n
 * @return {string[][]}
 */
var solveNQueens = function(n) {
  const ans = [];
  const board = Array.from({ length: n }, () => Array(n).fill('.'));
  const col = Array(n).fill(false);
  const diag1 = Array(2 * n).fill(false); // row - col + n
  const diag2 = Array(2 * n).fill(false); // row + col

  const dfs = (r) => {
    if (r === n) {
      ans.push(board.map(row => row.join('')));
      return;
    }

    for (let c = 0; c < n; c++) {
      const d1 = r - c + n;
      const d2 = r + c;
      if (col[c] || diag1[d1] || diag2[d2]) continue;

      board[r][c] = 'Q';
      col[c] = diag1[d1] = diag2[d2] = true;

      dfs(r + 1);

      board[r][c] = '.';
      col[c] = diag1[d1] = diag2[d2] = false;
    }
  };

  dfs(0);
  return ans;
};

Comments