51. N 皇后
题目描述
n 皇后问题 研究的是如何将 n
个皇后放置在 n×n
的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n
,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q'
和 '.'
分别代表了皇后和空位。
示例1:
输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。
示例 2:
输入:n = 1
输出:[["Q"]]
提示:
- 1 ≤ n ≤ 9 1 \le n \le 9 1≤n≤9
- 皇后彼此不能相互攻击,也就是说:任何两个皇后都不能处于同一条横行、纵行或斜线上。
题解:
法一:
普通回溯。
这题的关键是在搜索时候怎么快速判断 行、列、对角线
上是否已经放置了皇后。
假设当前位置为 (r, c)
,行和列就不用说了,使用两个数组记录就行了,而对角线比较麻烦。
但是,从左下到右上的每条对角线,可以发现对角线上的点都满足 n - r + c
是定值且唯一,也就意味着我们可以开辟一个数组,根据 n - r + c
作为下标来记录对角线上是否已经放置皇后了。同理左上到右下的每条对角线,对角线上的点都满足 r + c
是定值且唯一,我们可以开辟一个数组来记录放置皇后的状态。
这样的话,我们需要 4
个数组来记录状态。那么可以写出来一份 很蠢的代码:
时间复杂度: O ( 2 n 2 ) O(2^{n^2}) O(2n2)
class Solution {
public:
vector<bool> row, col, diag, rdiag;
vector<vector<string>> ret;
vector<string> ans;
void dfs( int x, int y, int num, int n ) {
if ( y == n ) ++x, y = 0;
if ( x >= n ) {
if ( num >= n ) ret.push_back( ans );
return;
}
dfs( x, y + 1, num, n );
if ( !row[x] && !col[y] && !diag[n - x + y] && !rdiag[x + y] ) {
row[x] = col[y] = diag[n - x + y] = rdiag[x + y] = true;
ans[x][y] = 'Q';
dfs( x, y + 1, num + 1, n );
row[x] = col[y] = diag[n - x + y] = rdiag[x + y] = false;
ans[x][y] = '.';
}
}
vector<vector<string>> solveNQueens(int n) {
ans = vector<string>( n, string(n, '.') );
row = vector<bool>( n );
col = vector<bool>( n );
diag = vector<bool>( n << 1 );
rdiag = vector<bool>( n << 1 );
dfs( 0, 0, 0, n );
return ret;
}
};
/*
时间:240ms,击败:5.26%
内存:8.6MB,击败:31.52%
*/
可以看到,速度出奇的慢2333。
优化:
其实可以只使用 3
个数组就行了,我们可以按 行优先,依次在每行中放置一个皇后,可以省去一个数组。而且时间复杂度变为
O
(
n
!
)
O(n!)
O(n!)
class Solution {
public:
vector<bool> col, diag, rdiag;
vector<vector<string>> ret;
vector<string> ans;
void dfs( int row, int n ) {
if ( row >= n ) {
ret.push_back( ans );
return;
}
for ( int i = 0; i < n; ++i ) {
if ( col[i] || diag[n - row + i] || rdiag[row + i] ) continue;
ans[row][i] = 'Q';
col[i] = diag[n - row + i] = rdiag[row + i] = true;
dfs( row + 1, n );
ans[row][i] = '.';
col[i] = diag[n - row + i] = rdiag[row + i] = false;
}
}
vector<vector<string>> solveNQueens(int n) {
ans = vector<string>( n, string(n, '.') );
col = vector<bool>( n );
diag = vector<bool>( n << 1 );
rdiag = vector<bool>( n << 1 );
dfs( 0, n );
return ret;
}
};
/*
时间:0ms,击败:100.00%
内存:7.2MB,击败:96.69%
*/
再优化:
由于
1
≤
n
≤
9
1 \le n \le 9
1≤n≤9 ,可以不开辟额外的数组,因为 int
变量除去符号位,有 31
位,而我们的对角线下标最多为 r + c = 9 + 9 = 18
,使用 int
变量来记录状态完全可行。
class Solution {
public:
int col, diag, rdiag;
vector<vector<string>> ret;
vector<string> ans;
void dfs( int row, int n ) {
if ( row >= n ) {
ret.push_back( ans );
return;
}
for ( int i = 0; i < n; ++i ) {
if ( (col >> i & 1) || (diag >> (n - row + i) & 1) || (rdiag >> (row + i) & 1) ) continue;
ans[row][i] = 'Q';
col ^= 1 << i;
diag ^= 1 << (n - row + i);
rdiag ^= 1 << (row + i);
dfs( row + 1, n );
ans[row][i] = '.';
col ^= 1 << i;
diag ^= 1 << (n - row + i);
rdiag ^= 1 << (row + i);
}
}
vector<vector<string>> solveNQueens(int n) {
ans = vector<string>( n, string(n, '.') );
col = diag = rdiag = 0;
dfs( 0, n );
return ret;
}
};
/*
时间:0ms,击败:100.00%
内存:7.3MB,击败:94.61%
*/
不过相比于开辟的 bool
数组,空间优化不明显。
法二:
在 方法一 中,一直都有同一个限制效率的因素:在搜索树的深处,能够放皇后的位置其实很少了,但我们一直都要试探每一列。
能不能直接得知每一行上还有哪些列能够放置皇后,免去这个枚举的过程呢?
位运算恰恰提供了这个可能!
使用三个变量 row, diag, rdiag
:
row
表示当前行哪些列已经被占用diag
和rdiag
表示当前行中,哪些对角线被其它行影响不能放皇后- 初始时
row = diag = rdiag = 0
比如:第 x
行有一个位置放了一个皇后:
row = 0 0 1 0 0 0 0 0
那么在第 x+1
行中,1
的左下方和右下方都不能在放置皇后。也就是:
diag = 0 0 0 1 0 0 0 0
rdiag= 0 1 0 0 0 0 0 0
并且,diag = row >> 1
,rdiag = row << 1
,可直接通过移位得到。
也就是说,如果第 x
行放的皇后位置为 p = 1 << k
,那么下一行中受 p
的影响,这些对角线是不能放皇后的:
diag = (diag | p) >> 1
rdiag= (rdiag| p) << 1
而在第 x
行,可放皇后的位置可以通过下面的计算公式直接得到:
safe = ((1 << n) - 1) & (~(row | diag | rdiag))
二进制表示中,1
表示可以放置,0
表示不可以放置,剩下的就是枚举 safe
中的 1
:
如果像下面这样的求 safe
中的每一位:
while ( x ) {
if ( x & 1 ) do { ... };
x >>= 1;
}
根本没法发挥出位运算的功效。
而 lowbit(x) = x & -x
恰好能求 x
的二进制表示中最后一位 1
代表的十进制数。可以这么求:
while ( x ) {
int nxt = x & -x;
do { ... };
x ^= nxt;
}
这样的话,我们就可以快速枚举可以放皇后的位置了。
最终的状态是: row = (1 << n) - 1
,表示所有列都放了一个皇后,找到了一个可行解。
class Solution {
public:
int col, diag, rdiag;
vector<vector<string>> ret;
vector<string> ans;
vector<int> map;
int goal;
void dfs( int r, int row, int diag, int rdiag ) {
if ( row == goal ) {
ret.push_back( ans );
return;
}
int safe = goal & (~(row | diag | rdiag));
while ( safe ) {
int nxt = safe & -safe;
safe ^= nxt;
ans[r][map[nxt]] = 'Q';
dfs( r + 1, row | nxt, (diag | nxt) << 1, (rdiag | nxt) >> 1 );
ans[r][map[nxt]] = '.';
}
}
vector<vector<string>> solveNQueens(int n) {
ans = vector<string>( n, string(n, '.') );
goal = (1 << n) - 1;
map.resize( 1 << n );
for ( int i = 0; i < n; ++i ) map[1 << i] = i;
dfs( 0, 0, 0, 0 );
return ret;
}
};
/*
时间:0ms,击败:100.00%
内存:7.1MB,击败:98.22%
*/