N 皇后问题是算法面试中的"常客",也是理解回溯算法(Backtracking)最经典的题目。很多初学者看到这道题,第一反应是"暴力穷举",但很快就会因为超时而放弃。其实,只要掌握了 "逐行放置" 和 "对角线数学映射" 这两个核心技巧,这道 Hard 级别的题目就能迎刃而解。
一、 题目核心与约束条件
题目要求在一个 n × n 的棋盘上放置 n 个皇后,使得它们彼此不能相互攻击。皇后的攻击范围极广,涵盖了:
- 同一行
- 同一列
- 同一主对角线(左上到右下)
- 同一副对角线(右上到左下)
二、 核心思路:降维打击与空间换时间
如果我们在 n × n 的网格里盲目尝试每个格子,时间复杂度会高得离谱。但我们可以利用规则进行降维 :
既然任意两个皇后不能同行,那么每一行必然且只能放置一个皇后。
这样一来,我们就不需要遍历二维网格了,只需要逐行递归 。在第 row 行,我们只需要尝试 0 到 n-1 列,检查当前位置是否安全即可。
为了保证 O(1) 的时间复杂度判断冲突,我们需要用三个布尔数组来记录"攻击范围":
cols[col]:记录第col列是否被占用。diag1[row - col]:记录主对角线是否被占用。diag2[row + col]:记录副对角线是否被占用。
三、 难点深度解析:对角线的数学映射
这是本题最劝退小白的地方,但其实背后的数学逻辑非常优美:
- 副对角线() :
观察棋盘,同一条副对角线上的格子,其行号 + 列号的值是恒定的。
例如:(0, 3),(1, 2),(2, 1),(3, 0),它们的row + col都等于 3。
所以,我们可以直接用row + col作为副对角线的唯一索引。 - 主对角线() :
同一条主对角线上的格子,其行号 - 列号的值是恒定的。
例如:(0, 0),(1, 1),(2, 2),它们的row - col都等于 0。
注意:row - col可能会出现负数(比如0 - 3 = -3),数组下标不能为负。因此,我们加上一个偏移量n - 1,将其映射到正数区间。最终索引为row - col + n - 1。
四、 完整代码实现(JavaScript)
理解了上述逻辑,代码其实就是"做选择 -> 递归 -> 撤销选择"的标准回溯模板:
ini
/**
* @param {number} n
* @return {string[][]}
*/
var solveNQueens = function(n) {
const res = [];
// 初始化棋盘,全部填充为 '.'
const board = Array.from({ length: n }, () => Array(n).fill('.'));
// 记录列、主对角线、副对角线的占用情况
const cols = new Array(n).fill(false);
const diag1 = new Array(2 * n - 1).fill(false); // 主对角线
const diag2 = new Array(2 * n - 1).fill(false); // 副对角线
// 回溯函数:从第 row 行开始放置皇后
const backtrack = (row) => {
// 终止条件:所有行都成功放置了皇后
if (row === n) {
// 将二维字符数组转换为字符串数组,加入结果集
res.push(board.map(row => row.join('')));
return;
}
// 在当前行的每一列尝试放置
for (let col = 0; col < n; col++) {
// 计算两条对角线的索引
const d1 = row - col + n - 1; // 主对角线
const d2 = row + col; // 副对角线
// 剪枝:如果当前列或对角线已被占用,直接跳过
if (cols[col] || diag1[d1] || diag2[d2]) {
continue;
}
// 1. 做选择:放置皇后,并标记占用
board[row][col] = 'Q';
cols[col] = true;
diag1[d1] = true;
diag2[d2] = true;
// 2. 递归:进入下一行
backtrack(row + 1);
// 3. 撤销选择(回溯):拿走皇后,恢复未占用状态
board[row][col] = '.';
cols[col] = false;
diag1[d1] = false;
diag2[d2] = false;
}
};
// 从第 0 行开始回溯
backtrack(0);
return res;
};
五、 复杂度分析
- 时间复杂度:O(N!)。第一行有 N 种选择,第二行最多有 N-1 种选择,依此类推。虽然实际运行中剪枝会跳过大量无效分支,但最坏情况下依然是阶乘级别。
- 空间复杂度:O(N)。主要是递归调用栈的深度(N 层)以及三个辅助数组的空间(O(N) + O(2N-1) + O(2N-1)),忽略存储结果的空间,额外空间为 O(N)。
六、 总结
N 皇后问题看似复杂,实则是对回溯思想 和数学映射能力 的双重考察。只要记住"逐行放置"的降维策略,以及 row+col 和 row-col+n-1 这两个对角线公式,你就能轻松写出优雅的解法。建议大家在看完解析后,自己动手敲一遍代码,感受"做选择"与"撤销选择"的对称美。