回溯之N皇后
https://leetcode.cn/problems/n-queens/
回顾上一期回溯算法的三件事:
- 站在一个节点上可以有哪些选择(选择列表)
- 站在一个节点上已经选择了哪些(路径)
- 站在一个节点上考虑还需不需要继续往前走(结束条件)
再之:回溯伪代码
void backTrack(路径,选择列表){
if(结束条件成立){
res.add(当前路径);
return;
}
//穷举
for(选择:选择列表){
//1.做选择
backTrack(路径,选择列表);
//2.撤销选择
}
}
针对本题,如何使用上述框架,首先要读懂题意,每个皇后都会和以自己为中心的一个米字形的路径上的皇后打架,如果棋盘弄小一点,我们常规的思路就是一行一行去放皇后,慢慢去穷举,最终得到结果.既然可以穷举出来,我们就可以考虑使用回溯算法了.框架还是这个框架,只不过路径和选择列表经常会出现各种表达形式,但本质都是一样的.都是需要我们做出选择后维护好选择过后的二者,已经递归完进行回溯的维护,为什么撤销选择,可以参考我全排列一文中的解释.
直接上代码,把注意事项都写在代码里:
class Solution {
List<List<String>> res = new LinkedList<>();
public List<List<String>> solveNQueens(int n) {
char[][] board = new char[n][n];
//初始化,没有皇后
for(char[] arr:board){
Arrays.fill(arr,'.');
}
backTrack(board,0);
return res;
}
//此处的考虑就是row代表要落皇后的当前行
//row之前的行已经选好了,所以一个row即充当了选择列表,又和board一起充当了路径
private void backTrack(char[][] board, int row) {
//结束条件
if(row==board.length){
//已经把length-1行都填好了,当然该结束了
res.add(helper(board));//因为我们在操作二维数组,所以需要进行一个转化
return;
}
//穷举
for(int col=0;col<board.length;col++){
if(!isValid(board,row,col)) continue;
//做选择
board[row][col]='Q';
//dfs深度优先
backTrack(board,row+1);
//撤销选择
board[row][col]='.';
}
}
private List<String> helper(char[][] board) {
List<String> list = new ArrayList<>();
for(char[] arr:board){
list.add(String.copyValueOf(arr));
}
return list;
}
private boolean isValid(char[][] board, int row, int col) {
//因为从选择的路径来看,我们是从上往下一行一行去在每一行选择一个位置作为新皇后的落子位置的
//所以为了避免打架,我们只需要检查这个新皇后会不会和"米"这个字的左上,正上,右上三方向的皇后打架
//这三个方向都没皇后,那就一切安全,米的下半部分不检查,因为还没放
//左上
for(int i=row-1,j=col-1;i>=0&&j>=0;i--,j--){
if(board[i][j]=='Q') return false;
}
//正上
for(int i=row-1;i>=0;i--){
if(board[i][col]=='Q') return false;
}
//右上
for(int i=row-1,j=col+1;i>=0&&j<board.length;i--,j++){
if(board[i][j]=='Q') return false;
}
return true;
}
}