一、题目
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
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
二、代码
class Solution {
public List<List<String>> solveNQueens(int n) {
// 记录所有的放置方法
List<List<String>> ans = new ArrayList<>();
// 记录尝试过程中,已放置的皇后位置
int[] queenCoordinate = new int[n];
// 递归入口
process(queenCoordinate, n, 0, ans);
return ans;
}
/*
在传参中带了List<List<String>> ans(地址传递),每一种可行的放置方法会存入这个对象中
*/
public void process(int[] queenCoordinate, int n, int row, List<List<String>> ans) {
// 当讲所有的行都遍历完成,说明这个递归分支的放置方法是可行的
if (row == n) {
// 当前方法放置皇后的坐标都在queenCoordinate变量中,根据这个变量将放置图写入到ans中
addQueenCoordinate(ans, queenCoordinate, n);
// 返回
return;
}
// 在第row行,尝试所有的列,看看是不是有效的放置皇后的位置(不与已放置的皇后起冲突)
for (int i = 0; i < n; i++) {
// 这里就是尝试过程中的无效检测,如果尝试的这个位置本身就是一个无效位置,那么直接就不会进入向下的递归,也就中断了。
if (isValid(row, i, queenCoordinate)) {
// 如果当前尝试的这个位置有效,那么就将这个位置设置成为皇后放置位置,然后以这个位置为基准,继续向下层递归,去下一行row + 1尝试
queenCoordinate[row] = i;
process(queenCoordinate, n, row + 1, ans);
// 注意在找到一种放置方案后,递归开始返回,需要还原queenCoordinate的数据,如果不清除上一种放置方法在queenCoordinate中存储的皇后位置,在后面的方法向里面存储皇后放置位置的时候,就会出现混乱的情况,导致两种方法的结果都混在了同一个queenCoordinate中,答案就不对了,所以这种就需要还原现场。
queenCoordinate[row] = 0;
}
}
return;
}
// 判断当前选择的这个摆放位置是不是有效的,会不会和已有的皇后起冲突
public boolean isValid(int x, int y, int[] queenCoordinate) {
// 0..i-1
// 传入的x和y就是要尝试位置的坐标,这个方法就是看这个坐标是不是有效的
// 遍历尝试的这一层以上的所有放置的皇后位置,然后去看尝试位置是不是与这些皇后位置冲突
for (int i = 0; i < x; i++) {
// 不用考虑行冲突,因为已经限定死了,每一行有且只能放一个皇后
// 只要纵坐标不相等,这两个位置就没有列冲突
// 去检查两个位置的横纵坐标相减的绝对值是不是一样,如果一样说明两个位置在同一条斜线上,就算是起冲突了。其实这个本质就是算两个位置与远点连线的斜率是否一致
int a = i;
int b = queenCoordinate[i];
if ((Math.abs(x - a) == Math.abs(y - b)) || y == b) {
return false;
}
}
return true;
}
// 根据queenCoordinate中存储的坐标位置,转换成放置图示存入ans中
// 每执行一次该方法,就会存储一种对整个棋盘的有效放置方法
public List<List<String>> addQueenCoordinate(List<List<String>> ans, int[] queenCoordinate, int n) {
// 对整个期盼的有效放置方法就存在rowAns中
List<String> rowAns = new ArrayList<>();
// 按棋盘从上到下,从左到右遍历
for (int row = 0; row < n; row++) {
// 棋盘每一行的放置情况就是存入一个String
StringBuilder sb = new StringBuilder();
for (int i = 0 ; i < n; i++) {
// 未放置皇后的位置赋值"."
if (queenCoordinate[row] != i) {
sb.append(".");
// 放置皇后的位置赋值"Q"
} else {
sb.append("Q");
}
}
rowAns.add(sb.toString());
}
// 将当前找出的放置方法存入到ans中
ans.add(rowAns);
return ans;
}
}
三、解题思路
这个题是一道非常经典的题目,整体就是使用暴力枚举的方法来求解,尝试所有的可能的放置情况,通过递归去做尝试,最终找到符合条件的放置方法。
这里就规定每一行有且只能放一个皇后,这样就可以不用考虑行冲突了,只需要考虑列和斜线冲突就行了。列冲突就是看纵坐标是否相同,斜线冲突就是看两个位置与原点连线的斜率是否相同,相同的话就是斜线冲突。按照这个规则就去尝试每一行的每一列,如果冲突了就跳过,如果没有起冲突就选定该位置,然后向下递归去尝试下一行。直到所有的位置都尝试一遍,整个过程就算是完事了。
时间复杂度:O(N^N)