题目描述
n−皇后问题是指将 n 个皇后放在 n×n的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。
现在给定整数 n,请你输出所有的满足条件的棋子摆法。
输入格式
共一行,包含整数 n。
输出格式
每个解决方案占 n行,每行输出一个长度为 n的字符串,用来表示完整的棋盘状态。其中 . 表示某一个位置的方格状态为空,Q 表示某一个位置的方格上摆着皇后。每个方案输出完成后,输出一个空行。
注意:行末不能有多余空格。输出方案的顺序任意,只要不重复且没有遗漏即可。
数据范围
1≤n≤9
输入样例:
4
输出样例:
.Q…
…Q
Q…
…Q.
…Q.
Q…
…Q
.Q…
题目分析
(1)分析
1. 边界条件: 行、列、主对角线、副对角线均只能有一个皇后
由于该题为各种排列的组合情况且可按照某种顺序进行排列,因此可选用DFS算法。
具体思路:从起始点出发,按行放置皇后,若选取的下一行中的位置其对应的列、主对角线和副对角线没有其余皇后与之冲突,才进行放置,放置完后,将对应的列、主对角线和副对角线标记为已有皇后。若不满足其上述的任意条件,则再查找下一个位置进行查找。
2. 对主对角线方向进行分析,
主对角线方向元素为:
A[0][n]、A[1][n-1]、…、A[n-1][1]、A[n][0];
A[0][n-1]、A[1][n-2]、…、A[n-2][1]、A[n][0;
A[1][n]、A[2][n-1]、…、A[n-1][2]、A[n][1];
…
设副主角线上元素的行下标为y,列下标为x,可发现主对角所在下标满足这样一种性质:
y + x = b,其中b为常数,即行列之和为一个行数。
3. 对副对角线方向进行分析,
副对角线反向元素为:
A[0][0]、A[1][1]、…、A[n-1][n-1]、A[n][n];
A[1][0]、A[2][1]、…、A[n-1][n-2]、A[n][n-1];
A[0][1]、A[1][2]、…、A[n-2][n-1]、A[n-1][n];
…
设副对角线上元素的行下标为y,列下标为x,可发现主对角所在下标满足这样一种性质:
y - x = b,其中b为常数,即行列之差为一个常数。
4. 推广
棋盘中的下标,相当于坐标系下离散化的点。同一斜线上的元素,相当于符合一元线性关系的函数上的值,因此通过确定行和列之间的关系,来得到一个很恒定的常量b。通过设立一个数组A,用其下标来表示不同斜线下对应的常量b,当同一斜线上已有元素时,就让A[b] = 1,进行标记。
在y = x + b这条线上,b = -x + y,遍历时搜索到的范围为这条线的下半部分,导致-x + y < 0,出现负数。为了防止出现负数,将其进行规格化为n - x + y,保证对角线上元素的相对值相同即可。
算法实现
实现方式一:固定顺序,按行搜索
#include <iostream>
using namespace std;
const int N = 10;
int n;
char g[N][N]; // 存储图中元素,图的几何结构为从下往上坐标依次递增
// col[i]: 存储对应列是否已有元素
// dig[i]: 存储副对角线上是否已有元素
// udig[i]:存储主对角线上是否已有元素
bool col[N], dig[N], udig[N];
// 行
void dfs(int u){
if(n == u){
for(int i = 0; i < n; i++) puts(g[i]); // 按行输出图
puts("");
return ;
}
for(int i = 0; i < n; i++){ // 按列搜索
if(!col[i] && !dig[u + i] && !udig[n - u + i]){ // 若列、主和副对角线上没有元素,则使用该位置
g[u][i] = 'Q';
col[i] = dig[u + i] = udig[n - u + i] = true; // 标记该位置已有元素
dfs(u + 1);
col[i] = dig[u + i] = udig[n - u + i] = false;
g[u][i] = '.';
}
}
}
int main(){
cin >> n;
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
g[i][j] = '.';
dfs(0);
return 0;
}
时间复杂度: O ( n ⋅ n ! ) O(n·n!) O(n⋅n!)
实现方式二:固定顺序,全局搜索
#include <iostream>
using namespace std;
const int N = 10;
int n;
char g[N][N];
// 记录行、列、正斜线、反斜线的情况
bool col[N], row[N], dig[N], udig[N];
void dfs(int y, int x, int s){ // 行、列和皇后个数
if(x == n) x = 0, y++; // 当这一行的所有列访问完后,访问下一行
if(y == n){ // 棋盘全部搜索完
if(s == n){ // 输出结果
for(int i = 0; i < n; i++) puts(g[i]);
puts("");
}
return ;
}
// 枚举不放皇后
g[y][x] = '.';
dfs(y, x + 1, s); // 不放置皇后,往下一列搜索
// 枚举放皇后
if(!row[y] && !col[x] && !dig[y + x] && !udig[n + y - x]){
g[y][x] = 'Q';
row[y] = col[x] = dig[y + x] = udig[n + y - x] = true;
dfs(y, x + 1, s + 1); // 放置皇后,往下一列搜索
row[y] = col[x] = dig[y + x] = udig[n + y - x] = false;
g[y][x] = '.';
}
}
int main(){
cin >> n;
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
g[i][j] = '.';
dfs(0, 0, 0);
return 0;
}
时间复杂度: O ( 2 n 2 ) O(2^{n^2}) O(2n2)
leetcode上N皇后
class Solution {
public:
vector<string> g;
vector<vector<string>> res;
void backtracking(int n, int row, vector<bool>& col, vector<bool>& dig, vector<bool>& udig) {
if(row == n) {
res.push_back(g);
return ;
}
for(int i = 0; i < n; i++) { // 按列来遍历
if(!col[i] && !dig[i + row] && !udig[n - i + row]) { // 该列、对应的主对角线、副对角线都没有放置皇后时,则可以放置
g[row][i] = 'Q';
col[i] = dig[i + row] = udig[n - i + row] = true;
backtracking(n, row + 1, col, dig, udig); // 递归下一行
col[i] = dig[i + row] = udig[n - i + row] = false;
g[row][i] = '.';
}
}
}
vector<vector<string>> solveNQueens(int n) {
vector<bool> col(n), dig(n), udig(n); // 列,主对角线、副对角线
for(int i = 0; i < n; i++) {
g.push_back(string(n, '.'));
}
backtracking(n, 0, col, dig, udig);
return res;
}
};
参考文章:
N皇后