LeetCode 51. N 皇后
摘要
LeetCode 51 N 皇后题解,采用回溯法在决策树上深度优先遍历,通过列、主对角线、副对角线三个剪枝数组排除冲突位置,枚举所有合法放置方案,时间复杂度 O(n!),空间复杂度 O(n × n!)(结果存储)。
标签
#算法 #LeetCode #回溯 #DFS #N皇后 #剪枝 #题解
目录
-
题目描述
-
易错点
-
思路
- 决策树模型
- 剪枝条件详解
- 状态回溯
-
编写代码
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. 易错点
- 对角线索引计算:主对角线
row - col可能为负,需要加上偏移n - 1使其非负;副对角线row + col范围在[0, 2n-2]。数组长度均为2n-1。 - 字符串修改:JavaScript 字符串不可变,需通过
slice拼接替换字符,不能直接赋值。 - 回溯状态恢复:递归返回后,需将棋盘恢复为
'.',同时将三个used数组对应的位置重置为false。 - 剪枝顺序:先检查列和对角线是否被占用,再放置皇后,避免无效递归。
- 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;
};