简介:
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n皇后问题研究的是如何将n个皇后放置在 n x n 的棋盘上,并且使皇后彼此之间不能互相攻击。
例题:
分析:
当 n = 4时,求解4皇后问题,在4x4的方格中放置皇后,我们可以先在第一个格子放入一个皇后,与此同时,已经不能在其同一行、同一列或同一斜线上再放置皇后。红色格子代表有冲突,不能放置。可以在第二行第3个格子再放置一个皇后,如下图。
由上图可知,当放入第二个皇后时第三行已经不能再放置其他皇后,我们需要放4个,显然皇后数量不够。因此我们需要把第二个皇后撤销,放入下一个格子。
现在第三行能放第三个皇后了。
但是此时所有方格全部冲突,也不行。
只能继续撤销找其他位置。依上述方式不断循环。
问题的关键是应该如何标记 当放入一个皇后之后 有冲突的格子。首先,皇后的同一行不需要记录冲突,因为一行只放一个皇后,不会连续放。
列冲突需要记录,可以用一个boolean类型的数组记录一列的冲突。有冲突的格子标记为true.
在 n x n的方格中,一共有 2n - 1 条左(右)斜线。
现在把方格的每一行定义为i , 每一列定义为 j。把当前方格的i 和 j相加(i + j)的结果放入本方格可以发现左斜线上的方格数字都相同,从0到6有七条斜线,因此可以用一个大小为7的boolean型数组来标记冲突区域,如果发现两个方格数字相同则表示冲突。
数字相同的单元格对应一条左(右)斜线。
(i + j) (i - j)
右图是i - j 的结果(右斜线),由于右图不太好对应索引,可以换成 n - 1 - (i - j) ,如下图:
n - 1 - (i - j)
首先,我们先要定义一个二维表格来表示棋盘,用3个一维数组分别表示列冲突,左斜线冲突和右斜线冲突。ca[ ] cb[ ] cc[ ] 。当放入一个皇后时,在此方格中填入字符 'Q',表示皇后,' . ' 表示没有皇后。
我们需要处理每一行皇后的放置,如果皇后放置时遇到冲突,需要跳过本次循环,进入下一次循环。这次递归方法中都没有合适的位置来放置,需要撤销上一步的放置,回溯到原来的状态。
皇后放置成功后要标记冲突的区域,标记为true。
因为第一行的每一列也要尝试放置皇后,所以要有一个针对列的循环。
最后考虑递归结束条件:当处理到第n行,表示找到了有效解。
代码实现:
package backtracking;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class NQueenLeetcode51 {
public static void main(String[] args) {
int n = 4;
boolean[] ca = new boolean[n]; //记录列冲突
boolean[] cb = new boolean[2 * n - 1]; //左斜线冲突
boolean[] cc = new boolean[2 * n - 1]; //右斜线冲突
char[][] table = new char[n][n];
for (char[] t : table) {
Arrays.fill(t, '.');
}
dfs(0, n, table, ca, cb, cc);
}
static void dfs(int i, int n, char[][] table, boolean[] ca, boolean[] cb, boolean[] cc){
if(i == n){ //当处理到第n行表示找到一个有效解
System.out.println("===================");
for (char[] t : table) {
System.out.println(new String(t));
}
return;
}
for (int j = 0; j < n; j++) {
if(ca[j] || cb[i + j] || cc[n - 1 - (i - j)]){
continue;
}
table[i][j] = 'Q';
ca[j] = cb[i + j] = cc[n - 1 - (i - j)] = true; //记录冲突
dfs(i + 1, n, table, ca, cb, cc);
table[i][j] = '.';
ca[j] = cb[i + j] = cc[n - 1 - (i - j)] = false;
}
}
}
Leetcode51题代码:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class NQueenLeetcode51 {
public static List<List<String>> solveNQueens(int n) {
List<List<String>> result = new ArrayList<>(n);
boolean[] ca = new boolean[n]; //记录列冲突
boolean[] cb = new boolean[2 * n - 1]; //左斜线冲突
boolean[] cc = new boolean[2 * n - 1]; //右斜线冲突
char[][] table = new char[n][n];
for (char[] t : table) {
Arrays.fill(t, '.');
}
dfs(0, n, table, result, ca, cb, cc);
return result;
}
public static void main(String[] args) {
int count = 0;
for (List<String> table : solveNQueens(4)) {
for (String row : table) {
System.out.println(row);
}
count++;
System.out.println("--------------------- " + count);
}
}
static void dfs(int i, int n, char[][] table, List<List<String>> result, boolean[] ca, boolean[] cb, boolean[] cc){
if(i == n){ //当处理到第n行表示找到一个有效解
List<String> list = new ArrayList<>();
for (char[] t : table) {
list.add(String.valueOf(t));
}
result.add(list);
return;
}
for (int j = 0; j < n; j++) {
if(ca[j] || cb[i + j] || cc[n - 1 - (i - j)]){
continue;
}
table[i][j] = 'Q';
ca[j] = cb[i + j] = cc[n - 1 - (i - j)] = true; //记录冲突
dfs(i + 1, n, table, result, ca, cb, cc);
table[i][j] = '.';
ca[j] = cb[i + j] = cc[n - 1 - (i - j)] = false;
}
}
}
运行结果:
小结:
解这道题有两个要点:
①:需要准备4个数组来表示棋盘的状态。
②:以行为单位尝试放入皇后,每一行内对不同列的位置进行尝试,最后无论有没有找到解都要在递归结束之后,把棋盘恢复成递归之前的状态。