回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。
回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。
设计一种算法,打印 N 皇后在 N × N 棋盘上的各种摆法,其中每个皇后都不同行、不同列,也不在对角线上。这里的“对角线”指的是所有的对角线,不只是平分整个棋盘的那两条对角线
输入输出示例
输入:4
输出:[[".Q…","…Q",“Q…”,"…Q."],["…Q.",“Q…”,"…Q",".Q…"]]
解释: 4 皇后问题存在如下两个不同的解法。
[
[".Q…", // 解法 1
“…Q”,
“Q…”,
“…Q.”],
["…Q.", // 解法 2
“Q…”,
“…Q”,
“.Q…”]
]
利用回溯+递归
的解法
变量说明:
解题思路
以下流程讲解当皇后数为n = 4的图解流程
.
为了方便观看,这里改为0.1.1 \quad 由上图可以看出第二行放置皇后的位置只能是 ( 1 , 2 ) 和 ( 1 , 3 ) (1, 2)和(1, 3) (1,2)和(1,3)的位置,尝试将第二个皇后放置在 r e s [ 1 ] [ 2 ] res[1][2] res[1][2]处,接着计算出 u s e d used used数组中剩下行不能放置皇后的位置
1.2 \quad 可以看出第三行没有能放置皇后空位置,所以第二次选放置皇后的位置行不通,返回至1步骤,尝试将第二个皇后放置在 r e s [ 1 ] [ 3 ] res[1][3] res[1][3]处,接着计算出 u s e d used used数组中剩下行不能放置皇后的位置
1.3 \quad 可以看出第三行的 ( 2 , 1 ) (2, 1) (2,1)处能够放置皇后,将第三个皇后放置在 r e s [ 2 ] [ 1 ] res[2][1] res[2][1]处,接着计算出 u s e d used used数组中剩下行不能放置皇后的位置
可以看出第四行没有能够放置皇后的位置,所以由第1步放置在位置 r e s [ 0 ] [ 0 ] res[0][0] res[0][0]是不能完成实现四个皇后的放置。此时回到最初的起点0
2 \quad 将第一个皇后放置在 r e s [ 0 ] [ 1 ] res[0][1] res[0][1]的位置,计算出 u s e d used used数组中剩下行不能放置皇后的位置
2.1 \quad 由上图可以看出第二行放置皇后的位置只能 ( 1 , 3 ) (1, 3) (1,3)的位置,将第二个皇后放置在 r e s [ 1 ] [ 3 ] res[1][3] res[1][3]处,接着计算出 u s e d used used数组中剩下行不能放置皇后的位置
2.2 \quad 由上图可以看出第三行放置皇后的位置只能 ( 2 , 0 ) (2, 0) (2,0)的位置,将第三个皇后放置在 r e s [ 2 ] [ 0 ] res[2][0] res[2][0]处,接着计算出 u s e d used used数组中剩下行不能放置皇后的位置
2.3 \quad 由上图可以看出第四行有且仅有一个位置放置皇后,故将第四个皇后放置在 r e s [ 3 ] [ 2 ] res[3][2] res[3][2]处,就能完成四个皇后的放置,满足题意。
到此为止,只寻找到一种放置的方法,只需要按此流程,继续寻找其它路线,即可找出所有满足题意的四个皇后的放置
class Solution0812 {
int total = 0;
public List<List<String>> solveNQueens(int n) {
List<List<String>> ans = new ArrayList<>(); // 存放结果的二维链表
int[][] used = new int[n][n]; // 根据题目条件推出不能放置皇后的的位置,跟res数组位置对应
char[][] res = new char[n][n]; // 放置皇后的二维数组
for (int i = 0; i < n; i++) { // 填充所有
Arrays.fill(res[i], '.');
}
solveNQueens(n, used, ans, 0, res); // 回溯法+递归
System.out.println(Arrays.deepToString(ans.toArray())); // 输出结果
System.out.println("total = " + total);
return ans;
}
// 回溯法+递归
public void solveNQueens(int n, int[][] used, List<List<String>> ans, int row, char[][] res) {
for (int column = 0; column < n; column++) { // 从第一行各个列开始向下尝试搜索
if (used[row][column] == 0) { // 位置为零则表示此处可以放置皇后
res[row][column] = 'Q';
if (row == n -1) { // 是否已经搜索到了最后一行,若是则表示一次搜索完成,将结果放置到ans中
List<String> temp = new ArrayList<>();
for (char[] re : res) {
temp.add(new String(re));
}
ans.add(temp);
res[row][column] = '.'; // 重置此位置 继续遍历当前行的下一位置
total++;
continue;
}
addValue(n, used, column, row, 1); // 计算不能插入皇后的位置
solveNQueens(n, used, ans, row + 1, res); // 下一行是否有空位置插入皇后
addValue(n, used, column, row, -1); // 若无空位置插入皇后,则返回上一步,回退
res[row][column] = '.';
}
}
}
// 当加入皇后,判断以下行不能插入皇后的位置
public void addValue(int n, int[][] used, int column, int row, int val) {
used[row][column] += val; // 当前位置已经插入了皇后
for (int j = 1; row + j < n; j++) {
used[row + j][column] += val; // 已经插入皇后的所在列不能再插入皇后,置1
if (column + j < n) used[row + j][column + j] += val; // 右下对角线不能插入皇后,置1
if (column - j >= 0) used[row + j][column - j] += val; // 左对角线不能插入皇后,置1
}
}
}