leetcode题解之52. N皇后 II
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

上图为 8 皇后问题的一种解法。
给定一个整数 n,返回 n 皇后不同的解决方案的数量。
示例:
输入: 4 输出: 2 解释: 4 皇后问题存在如下两个不同的解法。 [ [".Q..", // 解法 1 "...Q", "Q...", "..Q."], ["..Q.", // 解法 2 "Q...", "...Q", ".Q.."] ]
提示:
直观想法
这个问题是一个经典的问题,感受解法的优雅性很重要。
第一个想法是使用蛮力法,意味着生成在棋盘上放置 N 个皇后的所有可能的情况,并且检查是否保证没有皇后可以互相攻击。这意味着 的时间复杂度,因此我们必须考虑优化。
下面是两个有用的编程概念。
第一个叫做 约束编程.
它的基本含义是在放置每个皇后以后增加限制。当在棋盘上放置了一个皇后后,立即排除当前行,列和对应的两个对角线。该过程传递了 约束 从而有助于减少需要考虑情况数。

第二个叫做 回溯法.
我们来想象一下,当在棋盘上放置了几个皇后且不会相互攻击。但是选择的方案不是最优的,因为无法放置下一个皇后。此时我们该怎么做?回溯。意思是回退一步,来改变最后放置皇后的位置并且接着往下放置。如果还是不行,再 回溯。
方法1:回溯
在建立算法之前,我们来考虑两个有用的细节。
一行只可能有一个皇后且一列也只可能有一个皇后。
这意味着没有必要再棋盘上考虑所有的方格。只需要按列循环即可。
对于所有的主对角线有
行号 + 列号 = 常数,对于所有的次对角线有行号 - 列号 = 常数.
这可以让我们标记已经在攻击范围下的对角线并且检查一个方格 (行号, 列号) 是否处在攻击位置。

现在已经可以写回溯函数 backtrack(row = 0).
-
从第一个
row = 0开始. -
循环列并且试图在每个
column中放置皇后.-
如果方格
(row, column)不在攻击范围内- 在
(row, column)方格上放置皇后。 - 排除对应行,列和两个对角线的位置。
- If 所有的行被考虑过,
row == N- 意味着我们找到了一个解
- Else
- 继续考虑接下来的皇后放置
backtrack(row + 1).
- 继续考虑接下来的皇后放置
- 回溯:将在
(row, column)方格的皇后移除.
- 在
-
下面是上述算法的一个直接的实现。


































class Solution {
public boolean is_not_under_attack(int row, int col, int n,
int [] rows,
int [] hills,
int [] dales) {
int res = rows[col] + hills[row - col + 2 * n] + dales[row + col];
return (res == 0) ? true : false;
}
public int backtrack(int row, int count, int n,
int [] rows,
int [] hills,
int [] dales) {
for (int col = 0; col < n; col++) {
if (is_not_under_attack(row, col, n, rows, hills, dales)) {
// place_queen
rows[col] = 1;
hills[row - col + 2 * n] = 1; // "hill" diagonals
dales[row + col] = 1; //"dale" diagonals
<span class="hljs-comment">// if n queens are already placed</span>
<span class="hljs-keyword">if</span> (row + <span class="hljs-number">1</span> == n) count++;
<span class="hljs-comment">// if not proceed to place the rest</span>
<span class="hljs-keyword">else</span> count = backtrack(row + <span class="hljs-number">1</span>, count, n,
rows, hills, dales);
<span class="hljs-comment">// remove queen</span>
rows[col] = <span class="hljs-number">0</span>;
hills[row - col + <span class="hljs-number">2</span> * n] = <span class="hljs-number">0</span>;
dales[row + col] = <span class="hljs-number">0</span>;
}
}
<span class="hljs-keyword">return</span> count;
}
public int totalNQueens(int n) {
int rows[] = new int[n];
// "hill" diagonals
int hills[] = new int[4 * n - 1];
// "dale" diagonals
int dales[] = new int[2 * n - 1];
<span class="hljs-keyword">return</span> backtrack(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, n, rows, hills, dales);
}
}
class Solution:
def totalNQueens(self, n):
"""
:type n: int
:rtype: int
"""
def is_not_under_attack(row, col):
return not (rows[col] or hills[row - col] or dales[row + col])
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">place_queen</span><span class="hljs-params">(row, col)</span>:</span>
rows[col] = <span class="hljs-number">1</span>
hills[row - col] = <span class="hljs-number">1</span> <span class="hljs-comment"># "hill" diagonals</span>
dales[row + col] = <span class="hljs-number">1</span> <span class="hljs-comment"># "dale" diagonals</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">remove_queen</span><span class="hljs-params">(row, col)</span>:</span>
rows[col] = <span class="hljs-number">0</span>
hills[row - col] = <span class="hljs-number">0</span> <span class="hljs-comment"># "hill" diagonals</span>
dales[row + col] = <span class="hljs-number">0</span> <span class="hljs-comment"># "dale" diagonals</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">backtrack</span><span class="hljs-params">(row = <span class="hljs-number">0</span>, count = <span class="hljs-number">0</span>)</span>:</span>
<span class="hljs-keyword">for</span> col <span class="hljs-keyword">in</span> range(n):
<span class="hljs-keyword">if</span> is_not_under_attack(row, col):
place_queen(row, col)
<span class="hljs-keyword">if</span> row + <span class="hljs-number">1</span> == n:
count += <span class="hljs-number">1</span>
<span class="hljs-keyword">else</span>:
count = backtrack(row + <span class="hljs-number">1</span>, count)
remove_queen(row, col)
<span class="hljs-keyword">return</span> count
rows = [<span class="hljs-number">0</span>] * n
hills = [<span class="hljs-number">0</span>] * (<span class="hljs-number">2</span> * n - <span class="hljs-number">1</span>) <span class="hljs-comment"># "hill" diagonals</span>
dales = [<span class="hljs-number">0</span>] * (<span class="hljs-number">2</span> * n - <span class="hljs-number">1</span>) <span class="hljs-comment"># "dale" diagonals</span>
<span class="hljs-keyword">return</span> backtrack()
复杂度分析
- 时间复杂度:. 放置第 1 个皇后有
N种可能的方法,放置两个皇后的方法不超过N (N - 2),放置 3 个皇后的方法不超过N(N - 2)(N - 4),以此类推。总体上,时间复杂度为 . - 空间复杂度: . 需要保存对角线和行的信息。
方法 2:使用 bitmap 回溯
如果你是在面试中 - 使用方法 1。
下面的算法有着相同的时间复杂度 。但是由于使用了位运算,可以运行得更快。
感谢这个算法的提出者 takaken.
为了便于理解该算法,下面的代码进行了逐步解释。
class Solution {
public int backtrack(int row, int hills, int next_row, int dales, int count, int n) {
/**
row: 当前放置皇后的行号
hills: 主对角线占据情况 [1 = 被占据,0 = 未被占据]
next_row: 下一行被占据的情况 [1 = 被占据,0 = 未被占据]
dales: 次对角线占据情况 [1 = 被占据,0 = 未被占据]
count: 所有可行解的个数
*/
<span class="hljs-comment">// 棋盘所有的列都可放置,</span>
<span class="hljs-comment">// 即,按位表示为 n 个 '1'</span>
<span class="hljs-comment">// bin(cols) = 0b1111 (n = 4), bin(cols) = 0b111 (n = 3)</span>
<span class="hljs-comment">// [1 = 可放置]</span>
<span class="hljs-keyword">int</span> columns = (<span class="hljs-number">1</span> << n) - <span class="hljs-number">1</span>;
<span class="hljs-keyword">if</span> (row == n) <span class="hljs-comment">// 如果已经放置了 n 个皇后</span>
count++; <span class="hljs-comment">// 累加可行解</span>
<span class="hljs-keyword">else</span> {
<span class="hljs-comment">// 当前行可用的列</span>
<span class="hljs-comment">// ! 表示 0 和 1 的含义对于变量 hills, next_row and dales的含义是相反的</span>
<span class="hljs-comment">// [1 = 未被占据,0 = 被占据]</span>
<span class="hljs-keyword">int</span> free_columns = columns & ~(hills | next_row | dales);
<span class="hljs-comment">// 找到可以放置下一个皇后的列</span>
<span class="hljs-keyword">while</span> (free_columns != <span class="hljs-number">0</span>) {
<span class="hljs-comment">// free_columns 的第一个为 '1' 的位</span>
<span class="hljs-comment">// 在该列我们放置当前皇后</span>
<span class="hljs-keyword">int</span> curr_column = - free_columns & free_columns;
<span class="hljs-comment">// 放置皇后</span>
<span class="hljs-comment">// 并且排除对应的列</span>
free_columns ^= curr_column;
count = backtrack(row + <span class="hljs-number">1</span>,
(hills | curr_column) << <span class="hljs-number">1</span>,
next_row | curr_column,
(dales | curr_column) >> <span class="hljs-number">1</span>,
count, n);
}
}
<span class="hljs-keyword">return</span> count;
}
public int totalNQueens(int n) {
return backtrack(0, 0, 0, 0, 0, n);
}
}
class Solution:
def totalNQueens(self, n):
"""
:type n: int
:rtype: int
"""
def backtrack(row = 0, hills = 0, next_row = 0, dales = 0, count = 0):
"""
:type row: 当前放置皇后的行号
:type hills: 主对角线占据情况 [1 = 被占据,0 = 未被占据]
:type next_row: 下一行被占据的情况 [1 = 被占据,0 = 未被占据]
:type dales: 次对角线占据情况 [1 = 被占据,0 = 未被占据]
:rtype: 所有可行解的个数
"""
if row == n: # 如果已经放置了 n 个皇后
count += 1 # 累加可行解
else:
# 当前行可用的列
# ! 表示 0 和 1 的含义对于变量 hills, next_row and dales的含义是相反的
# [1 = 未被占据,0 = 被占据]
free_columns = columns & ~(hills | next_row | dales)
<span class="hljs-comment"># 找到可以放置下一个皇后的列</span>
<span class="hljs-keyword">while</span> free_columns:
<span class="hljs-comment"># free_columns 的第一个为 '1' 的位</span>
<span class="hljs-comment"># 在该列我们放置当前皇后</span>
curr_column = - free_columns & free_columns
<span class="hljs-comment"># 放置皇后</span>
<span class="hljs-comment"># 并且排除对应的列</span>
free_columns ^= curr_column
count = backtrack(row + <span class="hljs-number">1</span>,
(hills | curr_column) << <span class="hljs-number">1</span>,
next_row | curr_column,
(dales | curr_column) >> <span class="hljs-number">1</span>,
count)
<span class="hljs-keyword">return</span> count
<span class="hljs-comment"># 棋盘所有的列都可放置,</span>
<span class="hljs-comment"># 即,按位表示为 n 个 '1'</span>
<span class="hljs-comment"># bin(cols) = 0b1111 (n = 4), bin(cols) = 0b111 (n = 3)</span>
<span class="hljs-comment"># [1 = 可放置]</span>
columns = (<span class="hljs-number">1</span> << n) - <span class="hljs-number">1</span>
<span class="hljs-keyword">return</span> backtrack()
复杂度分析
- 时间复杂度:.
- 空间复杂度:.

浙公网安备 33010602011771号