1.题目链接:
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
•示例 1:
输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]] 解释:如上图所示,4 皇后问题存在两个不同的解法。•
示例 2:
输入:n = 1
输出:[["Q"]]
• 提示:
1 <= n <= 9
3. 解法:
算法思路:
首先,我们在第一行放置第一个皇后,然后遍历棋盘的第二行,在可行的位置放置第二个皇后,然后再遍历第三行,在可行的位置放置第三个皇后,以此类推,直到放置了 n 个皇后为止。
我们需要用一个数组来记录每一行放置的皇后的列数。在每一行中,我们尝试放置一个皇后,并检查是否会和前面已经放置的皇后冲突。如果没有冲突,我们就继续递归地放置下一行的皇后,直到所有的皇后都放置完毕,然后把这个方案记录下来。
在检查皇后是否冲突时,我们可以用一个数组来记录每一列是否已经放置了皇后,并检查当前要放置的皇后是否会和已经放置的皇后冲突。对于对角线,我们可以用两个数组来记录从左上角到右下角的每一条对角线上是否已经放置了皇后,以及从右上角到左下角的每一条对角线上是否已经放置了皇后。
•对于对角线是否冲突的判断可以通过以下流程解决:
1. 从左上到右下:相同对角线的行列之差相同;
2. 从右上到左下:相同对角线的行列之和相同。
因此,我们需要创建用于存储解决方案的二维字符串数组 solutions ,用于存储每个皇后的位置的一维整数数组 queens ,以及用于记录每一列和对角线上是否已经有皇后的布尔型数组
columns 、diagonals1 和 diagonals2 。
递归函数设计:void dfs(vector<vector<string>> &solutions, vector<int> &queens, int &n, int row, vector<bool> &columns, vector<bool> &diagonals1, vector<bool> &diagonals2)
参数:row(当前需要处理的行数);
返回值:无;
函数作用:在当前行放入一个不发生冲突的皇后,查找所有可行的方案使得放置 n 个皇后后不发生冲突。
递归函数流程如下:
1. 结束条件:如果 row 等于 n ,则表示已经找到一组解决方案,此时将每个皇后的位置存储到字符串数组 board 中,并将 board 存储到 solutions 数组中,然后返回;
2. 枚举当前行的每一列,判断该列、两个对角线上是否已经有皇后:
a. 如果有皇后,则继续枚举下一列;
b. 否则,在该位置放置皇后,并将 columns 、diagonals1 和 diagonals2 对应的位置设为 true ,表示该列和对角线上已经有皇后:
i. | 递归调用 dfs 函数,搜索下一行的皇后位置。如果该方案递归结束,则在回溯时需要将 |
columns 、diagonals1 和 diagonals2 对应的位置设为 false ,然后继续枚举下一列;
Java算法代码:
class Solution {
boolean[] checkCol,checkDig1,checkDig2;
List<List<String>> ret;
char[][] path;
int n;
public List<List<String>> solveNQueens(int _n) {
n = _n;
checkCol = new boolean[n];
checkDig1 = new boolean [n*2];
checkDig2 = new boolean [n*2];
ret = new ArrayList<>();
path = new char[n][n];
for(int i = 0;i < n; i++)
Arrays.fill(path[i],'.');
dfs(0);
return ret;
}
public void dfs(int row){
if(row == n){
//添加结果
List<String> tmp = new ArrayList<>();
for(int i = 0; i<n; i++){
tmp.add(new String(path[i]));
}
ret.add(new ArrayList<>(tmp));
}
for(int col = 0;col<n;col++){
//判断能不能放
if(checkCol[col] == false && checkDig1[row-col+n] == false && checkDig2[row+col] ==false){
path[row][col] = 'Q';
checkCol[col] = checkDig1[row-col+n] = checkDig2[row+col] = true;
dfs(row + 1);
//恢复现场
path[row][col] = '.';
checkCol[col] = checkDig1[row-col+n] = checkDig2[row+col] = false;
}
}
}
}
运行结果:
递归展开:
逻辑展开看细节:
细节1: 主对角线 和副对角线 是否被占据的判断
细节2: 所在行和列是否被占据的判断
细节3: 为什么有个 +4? 这里是为了偏移量都为正的。
细节4: 笔者找到了可以优化的点(实际上代码已经写了),但是真正要理解,还是得掌握细节。
注意:笔者这里有点问题,行和列搞反了(但是完全不影响理解题目,因为代码可以反过来写)
逻辑展开:
当掌握了一定的细节,后面就可以直接眼睛瞪结果。
重点看主副对角线,以及当前行和列是否会冲突的判断。
---------------------------------------------------------------------------------------------------------------------------------
记住,相信你的递归函数,它可以做到!
记住,不理解时候,去尝试手动展开!
记住,逻辑展开(你不可能对所有的题目都进行手动展开)!