LeetCode 51. N 皇后

3 阅读4分钟

LeetCode 51. N 皇后

摘要

LeetCode 51 N 皇后题解,采用回溯法在决策树上深度优先遍历,通过列、主对角线、副对角线三个剪枝数组排除冲突位置,枚举所有合法放置方案,时间复杂度 O(n!),空间复杂度 O(n × n!)(结果存储)。

标签

#算法 #LeetCode #回溯 #DFS #N皇后 #剪枝 #题解

目录

  1. 题目描述

  2. 易错点

  3. 思路

    • 决策树模型
    • 剪枝条件详解
    • 状态回溯
  4. 编写代码


1. 题目描述

按照国际象棋的规则,皇后可以攻击与之处在同一行、同一列或同一斜线上的棋子。

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。

示例 1:

text

输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:4 皇后问题存在两个不同的解法。

示例 2:

text

输入:n = 1
输出:[["Q"]]

提示:

  • 1 <= n <= 9

2. 易错点

  1. 对角线索引计算:主对角线 row - col 可能为负,需要加上偏移 n - 1 使其非负;副对角线 row + col 范围在 [0, 2n-2]。数组长度均为 2n-1
  2. 字符串修改:JavaScript 字符串不可变,需通过 slice 拼接替换字符,不能直接赋值。
  3. 回溯状态恢复:递归返回后,需将棋盘恢复为 '.',同时将三个 used 数组对应的位置重置为 false
  4. 剪枝顺序:先检查列和对角线是否被占用,再放置皇后,避免无效递归。
  5. n=1 特殊情况:只有一个格子,直接放置皇后,结果为一个 ["Q"]

3. 思路

决策树模型

N 皇后问题的决策树每层对应棋盘的一行(从第 0 行到第 n-1 行)。每个内部节点代表前 row 行已经放置了皇后,且满足约束条件。节点的分支表示在当前行 row 中尝试将皇后放置在某一列 col(0 到 n-1)。如果该位置与已放置的皇后不冲突,则进入下一层(下一行)。叶子节点(row === n)表示所有皇后都已成功放置,记录当前棋盘布局。

与全排列问题类似,每层都要尝试所有列,但不能重复使用同一列,且不能在同一对角线上。

剪枝条件详解

  • 列冲突usedCol[col] 标记第 col 列是否已有皇后。
  • 主对角线冲突(左上→右下) :同一主对角线上的格子满足 row - col 为常数。为避免负数索引,使用 d1 = row - col + n - 1,范围 [0, 2n-2]
  • 副对角线冲突(右上→左下) :同一副对角线上的格子满足 row + col 为常数。使用 d2 = row + col,范围 [0, 2n-2]

在尝试放置皇后时,若 usedCol[col]usedDiag1[d1] 或 usedDiag2[d2] 任一为 true,则跳过该位置。

状态回溯

  • 选择:标记列和对角线为已用,将棋盘当前位置改为 'Q'
  • 递归:进入下一行。
  • 撤销:恢复棋盘为 '.',重置列和对角线标记。

棋盘表示

初始棋盘为 n 个 '.' 组成的字符串数组。放置皇后时,通过 board[row] = board[row].slice(0, col) + 'Q' + board[row].slice(col + 1) 替换字符。回溯时用 '.' 替换回来。

复杂度

  • 时间复杂度:O(n!),每个位置尝试 n 种选择,总排列数接近 n!(受剪枝影响)。
  • 空间复杂度:O(n × n!),存储所有合法棋盘。

4. 编写代码

javascript 运行

javascript

var solveNQueens = function(n) {
    const ans = [];
    const board = Array.from({ length: n }, () => '.'.repeat(n));
    const usedCol = new Array(n).fill(false);
    const usedDiag1 = new Array(2 * n - 1).fill(false);
    const usedDiag2 = new Array(2 * n - 1).fill(false);

    const dfs = (row) => {
        if (row === n) {
            ans.push([...board]);
            return;
        }

        for (let col = 0; col < n; col++) {
            const d1 = row - col + n - 1;
            const d2 = row + col;

            if (usedCol[col] || usedDiag1[d1] || usedDiag2[d2]) continue;

            usedCol[col] = true;
            usedDiag1[d1] = true;
            usedDiag2[d2] = true;
            board[row] = board[row].slice(0, col) + 'Q' + board[row].slice(col + 1);

            dfs(row + 1);

            board[row] = board[row].slice(0, col) + '.' + board[row].slice(col + 1);
            usedCol[col] = false;
            usedDiag1[d1] = false;
            usedDiag2[d2] = false;
        }
    };

    dfs(0);
    return ans;
};