一、题目
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
上图为 8 皇后问题的一种解法。
给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。 每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
示例:
输入:4
输出:[
[".Q..", // 解法 1
"...Q",
"Q...",
"..Q."],
["..Q.", // 解法 2
"Q...",
"...Q",
".Q.."]
]
解释: 4 皇后问题存在两个不同的解法。
提示:
- 皇后彼此不能相互攻击,也就是说:任何两个皇后都不能处于同一条横行、纵行或斜线上。
二、解决
1、DFS
版本1
思路:
给定一个N*N方格Grid,从Grid[0,0]开始,逐行向下扫描,若一行中某个格子存在合法的位置,记录下来,然后继续往下扫描。
中间有不合法格子则中断,回到上一层的后一个格子,然后再逐层往下扫描,扫描到最后一行后,若存在合法的位置,则记录下来。
下面是一个4*4的回溯状态树,来自参考5,具体回溯过程可以观看其中的视频,比较详细。
代码:
public List<List<String>> solveNQueens(int n) {
List<List<String>> res = new LinkedList<List<String>>();
int[] usedCols = new int[n];// usedCols[i]: Column of the queen in row i
Arrays.fill(usedCols, -1); // 数组usedCols每个元素都填上-1
DFS(usedCols, 0, res);
return res;
}
void DFS(int[] usedCols, int row, List<List<String>> res) {
int n = usedCols.length;
if (row == n) {
res.add(drawGrids(usedCols));
return;
}
// 扫描当前行的每一列
for (int col = 0; col < n; col++) {
// 对该格进行判断,有效则记录并继续扫描下一行
if (isValid(usedCols, row, col)) {
usedCols[row] = col;
DFS(usedCols, row + 1, res);// Move on to the next row
}
}
}
// Check if the column is valid to place queen for the row.
boolean isValid(int[] usedCols, int row, int col) {
for (int i = 0; i < row; i++) {
// 检查前面使用的行是否影响该格 (row, col)
// ==> x0 - x == abs(y0 - y)
if (usedCols[i] == col || row - i == Math.abs(col - usedCols[i]))
return false;
}
return true;
}
List<String> drawGrids(int[] usedCols) {
List<String> res = new LinkedList<>();
for (int i : usedCols) {
char[] line = new char[usedCols.length];
Arrays.fill(line, '.');
line[i] = 'Q';
res.add(String.valueOf(line));
}
return res;
}
时间复杂度: O ( n ! ) O(n!) O(n!),n为皇后数量。
空间复杂度: O ( n ) O(n) O(n)
版本2
思路: 同版本1.
代码:
class Solution {
private Set<Integer> cols = new HashSet<>();
private Set<Integer> diag1 = new HashSet<>();
private Set<Integer> diag2 = new HashSet<>();
public List<List<String>> solveNQueens(int n) {
List<String> resUnit = new LinkedList<>();
List<List<String>> res = new LinkedList<>();
DFS(0, n, resUnit, res);
return res;
}
private void DFS(int row, int n, List<String> resUnit, List<List<String>> res) {
if (row==n) {
res.add(new ArrayList<>(resUnit));
return;
}
for(int col=0; col<n; col++) {
if (cols.contains(col) || diag1.contains(row+col) || diag2.contains(row-col)) continue;
char[] line = new char[n];
Arrays.fill(line, '.');
line[col] = 'Q';
resUnit.add(String.valueOf(line));
cols.add(col);
diag1.add(row+col);
diag2.add(row-col);
DFS(row+1, n, resUnit, res);
resUnit.remove(resUnit.size()-1);
cols.remove(col);
diag1.remove(row+col);
diag2.remove(row-col);
}
}
}
时间复杂度: O ( n ! ) O(n!) O(n!)
空间复杂度: O ( n ) O(n) O(n)
版本3
思路: 同版本1。
代码:
class Solution {
boolean[] cols;
boolean[] diag1;
boolean[] diag2;
public List<List<String>> solveNQueens(int n) {
cols = new boolean[n];
diag1 = new boolean[2*n];
diag2 = new boolean[2*n];
List<String> resUnit = new ArrayList<>();
List<List<String>> res = new ArrayList<>();
DFS(0, n, resUnit, res);
return res;
}
private void DFS(int row, int n, List<String> resUnit, List<List<String>> res) {
if (row==n) {
res.add(new ArrayList<>(resUnit));
return;
}
for (int col=0; col<n; col++) {
if (cols[col] || diag1[row+col] || diag2[row-col+n]) continue;
char[] line = new char[n];
Arrays.fill(line, '.');
line[col] = 'Q';
resUnit.add(new String(line));
cols[col]=true; diag1[row+col]=true; diag2[row-col+n]=true;
DFS(row+1, n, resUnit, res);
resUnit.remove(resUnit.size()-1);
cols[col]=false; diag1[row+col]=false; diag2[row-col+n]=false;
}
}
}
时间复杂度: O ( n ! ) O(n!) O(n!)
空间复杂度: O ( n ) O(n) O(n)
三、参考
1、Concise JAVA solution based on DFS
2、My easy understanding Java Solution
3、Comparably concise Java code
4、Share my JAVA DFS solution very easy to understand
5、回溯算法(转换成全排列问题 + 剪枝)- 题解后有相关问题
6、N皇后
7、java中数组的初始化(Arrays.fill() )
8、java中String数组和List的互相转化