文章目录
题目
标题和出处
标题:N 皇后
出处:51. N 皇后
难度
6 级
题目描述
要求
N 皇后问题 研究的是如何将 n \texttt{n} n 个皇后放置在 n × n \texttt{n} \times \texttt{n} n×n 的棋盘上,使皇后彼此之间不能相互攻击。
给定一个整数 n \texttt{n} n,返回所有不同的 N 皇后问题的方案。可以按任意顺序返回答案。
每一种方案包含一个不同的 N 皇后问题的棋子放置方案,其中 'Q' \texttt{`Q'} 'Q' 和 '.' \texttt{`.'} '.' 分别代表皇后和空位。
示例
示例 1:

输入: n = 4 \texttt{n = 4} n = 4
输出: \[".Q..","...Q","Q...","..Q.","..Q.","Q...","...Q",".Q.."] \texttt{\[".Q..","...Q","Q...","..Q.","..Q.","Q...","...Q",".Q.."]} \[".Q..","...Q","Q...","..Q.","..Q.","Q...","...Q",".Q.."]
解释: 4 \texttt{4} 4 皇后问题存在两个不同的方案,如上图所示。
示例 2:
输入: n = 1 \texttt{n = 1} n = 1
输出: \["Q"] \texttt{\["Q"]} \["Q"]
数据范围
- 1 ≤ n ≤ 9 \texttt{1} \le \texttt{n} \le \texttt{9} 1≤n≤9
解法一
思路和算法
根据 N 皇后问题的规则,任意两个皇后不能在同一行、同一列或同一条斜线上,因此每行必须放置一个皇后。为了得到 N 皇后的全部方案,需要依次在棋盘的每一行放置一个皇后,确保新放置的皇后和已经放置的皇后都不在同一列或同一条斜线上。斜线有两个方向,分别是从左上到右下和从右上到左下,为方便表述,将从左上到右下方向的斜线称为方向一斜线,将从右上到左下的斜线称为方向二斜线。
为了确保任意两个皇后之间不能相互攻击,需要维护三个哈希集合分别存储已经放置的皇后所在的列集合、方向一斜线集合与方向二斜线集合,每个集合中存储的元素如下。
-
列集合中存储已经放置的皇后所在的列下标。
-
由于同一条方向一斜线满足行下标与列下标之差为定值,因此方向一斜线集合中存储已经放置的皇后所在的行下标与列下标之差。
-
由于同一条方向二斜线满足行下标与列下标之和为定值,因此方向二斜线集合中存储已经放置的皇后所在的行下标与列下标之和。
可以使用回溯模拟依次在棋盘的每一行放置一个皇后的过程。用 row \textit{row} row 表示行下标,初始时 row = 0 \textit{row} = 0 row=0。回溯的做法如下。
-
如果 row = n \textit{row} = n row=n,则棋盘上已经放置 n n n 个皇后,根据每个皇后所在的行下标和列下标生成棋子放置方案并添加到结果中。
-
如果 row < n \textit{row} < n row<n,则需要在当前行 row \textit{row} row 放置一个皇后。用 column \textit{column} column 表示皇后所在的列下标, 0 ≤ column < n 0 \le \textit{column} < n 0≤column<n,对于每个 column \textit{column} column,如果位置 ( row , column ) (\textit{row}, \textit{column}) (row,column) 和已经放置的皇后都不在同一列或同一条斜线上,则在位置 ( row , column ) (\textit{row}, \textit{column}) (row,column) 处放置一个皇后,继续对行下标 row + 1 \textit{row} + 1 row+1 回溯。
回溯结束时,即可得到所有不同的 N 皇后问题的方案。
为了生成棋子放置方案,需要记录每行的皇后所在的列下标,当棋盘上放置 n n n 个皇后时,根据每行的皇后所在的列下标生成棋子放置方案的列表。
代码
java
class Solution {
List<List<String>> solutions = new ArrayList<List<String>>();
int n;
int[] queens;
public List<List<String>> solveNQueens(int n) {
this.n = n;
this.queens = new int[n];
Arrays.fill(queens, -1);
Set<Integer> columnSet = new HashSet<Integer>();
Set<Integer> diagonal1Set = new HashSet<Integer>();
Set<Integer> diagonal2Set = new HashSet<Integer>();
backtrack(0, columnSet, diagonal1Set, diagonal2Set);
return solutions;
}
public void backtrack(int row, Set<Integer> columnSet, Set<Integer> diagonal1Set, Set<Integer> diagonal2Set) {
if (row == n) {
solutions.add(getSolution());
} else {
for (int column = 0; column < n; column++) {
int diagonal1 = row - column, diagonal2 = row + column;
if (columnSet.contains(column) || diagonal1Set.contains(diagonal1) || diagonal2Set.contains(diagonal2)) {
continue;
}
queens[row] = column;
columnSet.add(column);
diagonal1Set.add(diagonal1);
diagonal2Set.add(diagonal2);
backtrack(row + 1, columnSet, diagonal1Set, diagonal2Set);
queens[row] = -1;
columnSet.remove(column);
diagonal1Set.remove(diagonal1);
diagonal2Set.remove(diagonal2);
}
}
}
public List<String> getSolution() {
List<String> solution = new ArrayList<String>();
for (int i = 0; i < n; i++) {
char[] arr = new char[n];
Arrays.fill(arr, '.');
arr[queens[i]] = 'Q';
solution.add(new String(arr));
}
return solution;
}
}
复杂度分析
-
时间复杂度: O ( n 2 × n ! ) O(n^2 \times n!) O(n2×n!),其中 n n n 是皇后数量。由于任意两个皇后都不能放置在同一列,因此放置 n n n 个皇后的方案数最多是 n ! n! n!,对于每种放置方案需要 O ( n 2 ) O(n^2) O(n2) 的时间生成答案,因此时间复杂度是 O ( n 2 × n ! ) O(n^2 \times n!) O(n2×n!)。
-
空间复杂度: O ( n ) O(n) O(n),其中 n n n 是皇后数。哈希集合、存储每个皇后所在的列下标的数组和递归调用栈需要 O ( n ) O(n) O(n) 的空间。
解法二
思路和算法
解法一使用三个哈希集合存储已经放置的皇后所在的列集合、方向一斜线集合与方向二斜线集合,也可以使用三个二进制数代替三个哈希集合。用 columnMask \textit{columnMask} columnMask、 diagonal 1 Mask \textit{diagonal}_1\textit{Mask} diagonal1Mask 和 diagonal 2 Mask \textit{diagonal}_2\textit{Mask} diagonal2Mask 分别记录已经放置的皇后所在的列集合、方向一斜线集合与方向二斜线集合,每个二进制整数的每一位是 1 1 1 或 0 0 0。
-
1 1 1 表示该位置和已经放置的皇后在同一列或同一条斜线上,该位置不能放置皇后。
-
0 0 0 表示该位置和已经放置的皇后都不在同一列或同一条斜线上,该位置可以放置皇后。
当遍历到行下标 row \textit{row} row 且 row < n \textit{row} < n row<n 时,用 column \textit{column} column 表示皇后所在的列下标, 0 ≤ column < n 0 \le \textit{column} < n 0≤column<n,记 columnBit = 2 column \textit{columnBit} = 2^{\textit{column}} columnBit=2column。如果在位置 ( row , column ) (\textit{row}, \textit{column}) (row,column) 放置皇后,则三个二进制数的更新如下。
-
列集合二进制数 columnMask \textit{columnMask} columnMask 更新为 columnMask ∣ columnBit \textit{columnMask} ~|~ \textit{columnBit} columnMask ∣ columnBit,表示后面的每一行的列下标 column \textit{column} column 处不能放置皇后。
-
方向一斜线集合二进制数 diagonal 1 Mask \textit{diagonal}_1\textit{Mask} diagonal1Mask 更新为 ( diagonal 1 Mask ∣ columnBit ) < < 1 (\textit{diagonal}_1\textit{Mask} ~|~ \textit{columnBit}) << 1 (diagonal1Mask ∣ columnBit)<<1,表示行下标 row + k \textit{row} + k row+k( k > 0 k > 0 k>0)的列下标 column + k \textit{column} + k column+k 处不能放置皇后。
-
方向二斜线集合二进制数 diagonal 2 Mask \textit{diagonal}_2\textit{Mask} diagonal2Mask 更新为 ( diagonal 2 Mask ∣ columnBit ) > > 1 (\textit{diagonal}_2\textit{Mask} ~|~ \textit{columnBit}) >> 1 (diagonal2Mask ∣ columnBit)>>1,表示行下标 row + k \textit{row} + k row+k( k > 0 k > 0 k>0)的列下标 column − k \textit{column} - k column−k 处不能放置皇后。
遍历到每一行时,根据三个二进制数的按位或结果判断当前行的每个列下标的位置是否可以放置皇后, 1 1 1 的位置不能放置皇后, 0 0 0 的位置可以放置皇后。为了快速枚举每个可以放置皇后的列下标,可以将三个二进制数的按位或结果取反之后取最低 n n n 位,得到二进制整数 availableColumns \textit{availableColumns} availableColumns,则 availableColumns \textit{availableColumns} availableColumns 中的每个 1 1 1 的位置可以放置皇后,然后使用 Brian Kernighan 算法枚举 availableColumns \textit{availableColumns} availableColumns 中的每个 1 1 1 的位置,即为每个可以放置皇后的列下标。
代码
java
class Solution {
List<List<String>> solutions = new ArrayList<List<String>>();
int n;
int[] queens;
public List<List<String>> solveNQueens(int n) {
this.n = n;
this.queens = new int[n];
Arrays.fill(queens, -1);
backtrack(0, 0, 0, 0);
return solutions;
}
public void backtrack(int row, int columnMask, int diagonal1Mask, int diagonal2Mask) {
if (row == n) {
solutions.add(getSolution());
} else {
int availableColumns = ((1 << n) - 1) & (~(columnMask | diagonal1Mask | diagonal2Mask));
while (availableColumns != 0) {
int columnBit = availableColumns & (-availableColumns);
availableColumns &= availableColumns - 1;
int column = Integer.bitCount(columnBit - 1);
queens[row] = column;
backtrack(row + 1, columnMask | columnBit, (diagonal1Mask | columnBit) << 1, (diagonal2Mask | columnBit) >> 1);
queens[row] = -1;
}
}
}
public List<String> getSolution() {
List<String> solution = new ArrayList<String>();
for (int i = 0; i < n; i++) {
char[] arr = new char[n];
Arrays.fill(arr, '.');
arr[queens[i]] = 'Q';
solution.add(new String(arr));
}
return solution;
}
}
复杂度分析
-
时间复杂度: O ( n 2 × n ! ) O(n^2 \times n!) O(n2×n!),其中 n n n 是皇后数量。由于任意两个皇后都不能放置在同一列,因此放置 n n n 个皇后的方案数最多是 n ! n! n!,对于每种放置方案需要 O ( n 2 ) O(n^2) O(n2) 的时间生成答案,因此时间复杂度是 O ( n 2 × n ! ) O(n^2 \times n!) O(n2×n!)。
-
空间复杂度: O ( n ) O(n) O(n),其中 n n n 是皇后数。存储每个皇后所在的列下标的数组和递归调用栈需要 O ( n ) O(n) O(n) 的空间。