「这是我参与2022首次更文挑战的第9天,活动详情查看:2022首次更文挑战」。
说在前面
N皇后是递归回溯算法中的经典题目,而说起N皇后,我们就会想想起经典的八皇后问题,八皇后问题(英文:Eight queens),是由国际象棋棋手马克斯·贝瑟尔于1848年提出的问题,是回溯算法的典型案例。
问题表述为:在8×8格的国际象棋上摆放8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。如果经过±90度、±180度旋转,和对角线对称变换的摆法看成一类,共有42类。计算机发明后,有多种计算机语言可以编程解决此问题。
同理现在可以将八皇后延伸至N皇后,也是一样的规则。
描述
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
示例 1:
输入:n = 4 输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]] 解释:如上图所示,4 皇后问题存在两个不同的解法。
思路
使用深搜和回溯的思路来进行解题,遍历整个棋盘,判断当前位置能否放置棋子,能放的话就给当前位置添加标记,然后往下一层继续搜索填充,不能放的话则进行回溯,回退到上一个标记点,取消标记并继续搜索,重新寻找满足条件的位置,不断循环这些步骤。
一行一行地摆放,在确定一行中的那个皇后应该摆在哪一列时,需要当前列是否合法,如果合法,则将皇后放置在当前位置,并进行递归,回溯。每行都摆满皇后时,则产生了一种解法,将所有解法收集并返回。 判断合法:当前将要摆放’Q’的位置和其他已摆放‘Q’的位置不能在同一列,且不能在同一条45度斜线或135度斜线上。这里判断是否在同一条斜线上可通过当前将要摆放’Q’的位置和其他已摆放‘Q’的位置横坐标之差和纵坐标之差的绝对值是否相等来判断。
八皇后问题如果用穷举法需要尝试8的8次方也就是16,777,216种情况。每一列放一个皇后,可以放在第 1 行,第 2 行,……,直到第8行。穷举的时候从所有皇后都放在第1行的方案开始,检验皇后之间是否会相互攻击。如果会,把列H的皇后挪一格,验证下一个方案。移到底了就“进位”到列G的皇后挪一格,列H的皇后重新试过全部的8行。这种方法是非常低效率的,因为它并不是哪里有冲突就调整哪里,而是盲目地按既定顺序枚举所有的可能方案。
回溯算法优于穷举法。将列A的皇后放在第一行以后,列B的皇后放在第一行已经发生冲突。这时候不必继续放列C的皇后,而是调整列B的皇后到第二行,继续冲突放第三行,不冲突了才开始进入列C。如此可依次放下列A至E的皇后,如图2所示。将每个皇后往右边横向、斜向攻击的点位用叉标记,发现列F的皇后无处安身。这时回溯到列E的皇后,将其位置由第4行调整为第8行,进入列F,发现皇后依然无处安身,再次回溯列E。此时列E已经枚举完所有情况,回溯至列D,将其由第2行移至第7行,再进入列E继续。按此算法流程最终找到如图3所示的解,成功在棋盘里放下了8个“和平共处”的皇后。继续找完全部的解共92个。
回溯算法求解N皇后问题的原则是:有冲突解决冲突,没有冲突往前走,无路可走往回退,走到最后是答案。为了加快有无冲突的判断速度,可以给每行和两个方向的每条对角线是否有皇后占据建立标志数组。放下一个新皇后做标志,回溯时挪动一个旧皇后清除标志。
代码
/**
* @param {number} n
* @return {string[][]}
*/
var solveNQueens = function(n) {
var t = [],
temp = [],
res = [];
for(let i = 0; i < n; i++){
t.push(0);
}
for(let i = 0; i < n; i++){
temp.push([...t]);
}
//判断能否摆放
var jungle = function(i,j,n){
//横
for(let k = 0;k < n; k++){
if(temp[i][k] == 1) return 0;
}
//竖
for(let k = 0;k < n; k++){
if(temp[k][j] == 1) return 0;
}
//斜
for(let k = 1; k + j < n && i - k >= 0; k++){
if(temp[i - k][j + k] == 1) return 0;
}
for(let k = 1; k + i < n && j - k >= 0; k++){
if(temp[i + k][j - k] == 1) return 0;
}
for(let k = 1; k + i < n && j + k < n; k++){
if(temp[i + k][j + k] == 1) return 0;
}
for(let k = 1; i - k >= 0 && j - k >= 0; k++){
if(temp[i - k][j - k] == 1) return 0;
}
return 1;
}
//深搜遍历
var dfs = function(e,n){
for(let i = 0;i < n; i++){
if(temp[e][i] == 0 && jungle(e,i,n)){
// console.log(e,i);
temp[e][i] = 1;
// console.log(temp);
if(e == n-1){
// console.log(temp);
var ans = [];
for(let i1 = 0; i1 < n; i1++){
var a = "";
for(let i2 = 0; i2 < n; i2++){
if(temp[i1][i2] == 0) a += ".";
else a += "Q";
}
ans.push(a);
}
res.push(ans);
}else{
dfs(e+1,n);
}
}
temp[e][i] = 0;
}
}
dfs(0,n);
return res;
};