LeetCode 51. N 皇后
📌 题目描述
题目级别:困难
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
💡 解法一:DFS 逐行放置与绝对值校验
这是回溯算法的最经典应用。我们不需要在一个 N × N N \times N N×N 的格子里一个个盲目去试,因为题目明确规定"同行不能有皇后"。
因此,我们可以按行进行 DFS 递归:
- 每一层递归负责在当前行
u中找一个合适的列i放置皇后。 - 放置前,通过
check函数校验是否合法。 - 这里的校验逻辑非常具有几何直觉:
- 同一列不冲突:
j != y。 - 同一斜线不冲突:利用斜率为 1 和 -1 的直线特性,如果两个点在同一条斜线上,它们的横坐标之差的绝对值必然等于纵坐标之差的绝对值,即
abs(i - x) != abs(j - y)。
- 同一列不冲突:
💻 C++ 代码实现 (精简修正版)
cpp
class Solution {
public:
vector<vector<string>> res;
vector<string> tmp;
vector<vector<string>> solveNQueens(int n) {
// 优雅初始化:生成一个 n 行,每行包含 n 个 '.' 的字符串数组
tmp = vector<string>(n, string(n, '.'));
// 从第 0 行开始深度优先搜索
dfs(0, n);
return res;
}
void dfs(int u, int n)
{
// 递归终止条件:已经成功放置到了第 n 行(0 到 n-1 都放好了)
if (u == n)
{
res.push_back(tmp);
return ;
}
// 尝试在当前行 u 的每一列 i 放置皇后
for (int i = 0; i < n; i ++ )
{
if (check(u, i, n))
{
tmp[u][i] = 'Q'; // 处理节点
dfs(u + 1, n); // 向下递归,处理下一行
tmp[u][i] = '.'; // 回溯:撤销处理,尝试下一列
}
}
}
// 校验在 (x, y) 放置皇后是否合法
bool check(int x, int y, int n)
{
// 只需要检查 x 行之前的行即可,因为下面的行还没放皇后
for (int i = 0; i < x; i ++ )
{
for (int j = 0; j < n; j ++ )
{
if (tmp[i][j] == 'Q')
{
// 检查列冲突 或 对角线冲突
if (j == y || abs(i - x) == abs(j - y)) return false;
}
}
}
return true;
}
};
💡 解法二:状态数组降维打击
在基础版本中,每次放置皇后都要回头遍历棋盘检查冲突,校验的时间复杂度是 O ( N 2 ) O(N^2) O(N2)。
为了追求极致性能,我们可以开辟三个布尔数组,专门记录哪些列、哪些斜线已经被占用了:
col数组 :记录哪一列有皇后。col[i] = true表示第i列被占。dg数组(正对角线) :观察棋盘发现,处于同一条正对角线(右上到左下,/)上的点,它们的行号和列号之和是固定的 (x + y = const)。我们用dg[u + i]来标记这条斜线。udg数组(反对角线) :处于同一条反对角线(左上到右下,\)上的点,它们的行号和列号之差是固定的 (y - x = const)。为了防止数组下标出现负数,我们加上一个偏移量n,即用udg[n - u + i]来标记这条斜线。
有了这三个数组,判断一个点能不能放皇后,只需要查三个布尔值即可,瞬间降维到 O ( 1 ) O(1) O(1)!
💻 C++ 代码实现 (极客优化版)
cpp
class Solution {
private:
vector<vector<string>> res;
vector<string> board;
// 状态数组:因为是对角线,最大数量是 2N-1,所以开大一点 20 足够应付 n=9
bool col[20], dg[20], udg[20];
void dfs(int u, int n) {
// 成功放置到最后一行,收集结果
if (u == n) {
res.push_back(board);
return;
}
// 尝试在第 u 行的每一列 i 放置皇后
for (int i = 0; i < n; i++) {
// O(1) 极速校验:该列、该正对角线、该反对角线均没有被占用
if (!col[i] && !dg[u + i] && !udg[n - u + i]) {
board[u][i] = 'Q';
// 同步更新三个状态数组,宣告占领
col[i] = dg[u + i] = udg[n - u + i] = true;
dfs(u + 1, n); // 向下递归
// 回溯:撤销皇后,并释放三个状态数组的占领
col[i] = dg[u + i] = udg[n - u + i] = false;
board[u][i] = '.';
}
}
}
public:
vector<vector<string>> solveNQueens(int n) {
board.assign(n, string(n, '.'));
// 初始化状态数组
memset(col, false, sizeof col);
memset(dg, false, sizeof dg);
memset(udg, false, sizeof udg);
dfs(0, n);
return res;
}
};