解法 1:经典回溯法(递归+二维棋盘检查)
核心思想:逐行放置皇后,检查每个位置是否与已有皇后冲突。
#include <vector>
#include <string>
class Solution {
public:
vector<vector<string>> solveNQueens(int n) {
vector<vector<string>> res;
vector<string> board(n, string(n, '.')); // 初始化棋盘
backtrack(board, 0, res);
return res;
}
private:
void backtrack(vector<string>& board, int row, vector<vector<string>>& res) {
if (row == board.size()) {
res.push_back(board); // 找到合法解
return;
}
for (int col = 0; col < board.size(); ++col) {
if (isValid(board, row, col)) {
board[row][col] = 'Q'; // 放置皇后
backtrack(board, row + 1, res); // 进入下一行
board[row][col] = '.'; // 回溯
}
}
}
// 检查 (row, col) 是否可以放置皇后
bool isValid(vector<string>& board, int row, int col) {
// 检查列
for (int i = 0; i < row; ++i)
if (board[i][col] == 'Q') return false;
// 检查左上对角线
for (int i = row-1, j = col-1; i >=0 && j >=0; --i, --j)
if (board[i][j] == 'Q') return false;
// 检查右上对角线
for (int i = row-1, j = col+1; i >=0 && j < board.size(); --i, ++j)
if (board[i][j] == 'Q') return false;
return true;
}
};
解法 2:位运算优化回溯(空间效率更高)
核心思想:用位掩码记录列和对角线的占用状态,减少判断时间。
#include <vector>
#include <string>
class Solution {
public:
vector<vector<string>> solveNQueens(int n) {
vector<vector<string>> res;
vector<string> board(n, string(n, '.'));
backtrack(0, 0, 0, 0, board, res, n);
return res;
}
private:
void backtrack(int row, int cols, int diag1, int diag2,
vector<string>& board, vector<vector<string>>& res, int n) {
if (row == n) {
res.push_back(board);
return;
}
// 计算当前行可用的列位置
int available = ((1 << n) - 1) & ~(cols | diag1 | diag2);
while (available) {
// 取最低位的 1 作为当前列
int col = __builtin_ctz(available & -available);
board[row][col] = 'Q';
// 递归下一行(更新列、主对角线、副对角线掩码)
backtrack(row + 1,
cols | (1 << col),
(diag1 | (1 << col)) << 1,
(diag2 | (1 << col)) >> 1,
board, res, n);
board[row][col] = '.'; // 回溯
available &= available - 1; // 移除最低位的 1
}
}
};
解法 3:基于排列的回溯(避免列冲突)
核心思想:每行皇后必在不同列,转化为寻找列索引的排列,并检查对角线。
#include <vector>
#include <string>
class Solution {
public:
vector<vector<string>> solveNQueens(int n) {
vector<vector<string>> res;
vector<int> cols; // 记录每行皇后的列索引
backtrack(cols, res, n);
return res;
}
private:
void backtrack(vector<int>& cols, vector<vector<string>>& res, int n) {
if (cols.size() == n) {
vector<string> board = generateBoard(cols, n);
res.push_back(board);
return;
}
for (int col = 0; col < n; ++col) {
if (isValid(cols, col)) {
cols.push_back(col);
backtrack(cols, res, n);
cols.pop_back(); // 回溯
}
}
}
// 检查新列是否合法(隐含行不同,只需检查列和对角线)
bool isValid(vector<int>& cols, int new_col) {
int new_row = cols.size();
for (int row = 0; row < cols.size(); ++row) {
int col = cols[row];
// 检查列冲突或对角线冲突
if (col == new_col || abs(new_row - row) == abs(new_col - col))
return false;
}
return true;
}
// 生成棋盘字符串
vector<string> generateBoard(vector<int>& cols, int n) {
vector<string> board(n, string(n, '.'));
for (int row = 0; row < n; ++row)
board[row][cols[row]] = 'Q';
return board;
}
};
解法对比分析
方法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
---|---|---|---|---|
经典回溯法 | O(n!) | O(n^2) | 直观,易理解 | 每次检查冲突效率低 |
位运算优化 | O(n!) | O(n) | 位操作极快,内存占用小 | 代码较难理解 |
基于排列的回溯 | O(n!) | O(n) | 避免列冲突检查,优化搜索空间 | 生成棋盘需要额外步骤 |
运行示例(以解法1为例)
输入 n = 4
,输出:
[
[".Q..", // 解法 1
"...Q",
"Q...",
"..Q."],
["..Q.", // 解法 2
"Q...",
"...Q",
".Q.."]
]
扩展优化思路
- 对称性剪枝:利用棋盘的对称性减少重复计算(如只计算一半,镜像生成其他解)。
- 并行回溯:在多核 CPU 上并行处理不同分支。
- 记忆化冲突检测:缓存已计算过的冲突状态(适合大规模 N 皇后问题)。
根据问题规模选择合适的实现:小规模用经典回溯(易读),大规模用位运算优化(高效)。