n皇后问题
我的第一版代码
class Solution {
List<List<String>> res = new ArrayList<>();
LinkedList<String> path = new LinkedList<>();
StringBuilder sb;
boolean[][] used;
boolean[] usedY;
public List<List<String>> solveNQueens(int n) {
if(n ==1){
path.add("Q");
res.add(new ArrayList(path));
return res;
}
used = new boolean[n][n];
usedY = new boolean[n];
backTracking(n,0);
return res;
}
public void backTracking(int n,int x){
sb = new StringBuilder();
//终止条件,达成目标的条件。
if(path.size() == n){
res.add(new ArrayList(path));
return;
}
for(int y = 0;y<n;y++){//不需要设定x,行是自动下移一位的
//如果当前列已经被占用了
if(usedY[y]){
continue;
}
//如果当前坐标对角线有其他棋子,continue
//1.在第一列的特殊情况,检查右上方即可,因为下面一排还没有放棋子,而同一列有usedY来检查,我们需要做的是检查对角线。
if(x>0 && y==0){
if(used[x-1][y+1]){
continue;
}
}
//最后一列的特殊情况
else if(x>0 && y == n-1){
if(used[x-1][y-1]){
continue;
}
}
//普通情况,检查左上角和右上角
else if(x > 0 && y > 0){
if(used[x-1][y-1] || used[x-1][y+1]){
continue;
}
}
//其他情况,当前坐标可用
//1.记录下当前的棋子字符串表达式,并放入path中
for(int j = 0;j<n;j++){
if(j == y){
sb.append("Q");
}
else{
sb.append(".");
}
}
path.add(sb.toString());
//将当前坐标标记为用过的
used[x][y] = true;
//将当前列标记为使用过的
usedY[y] = true;
//行向下移动
x++;
//进入递归
backTracking(n,x);
//回溯
//重置信息
x--;
usedY[y] = false;
used[x][y] = false;
path.removeLast();
}
}
}
该代码出现了错误,是因为我考虑斜线只考虑了一格的范围内,实际上整个棋盘上面的斜线都要考虑
那么要如何考虑一整个斜线上是否有多个棋子呢?
我们的斜线对应有左右斜线,发现左斜线如果用y-x,其分布的数值在[-n+1,n-1]之间,如果统一加上n-1,其数值就在[0,n-2]之间
右斜线,在同一条斜线上的数值,我们发现x+y都是相等的,且数值也处于[0,n-2]之间,因此,我们可以使用两个boolean数组来存放对应斜线是否存在棋子的信息。
class Solution {
List<List<String>> res = new ArrayList<>();
LinkedList<String> path = new LinkedList<>();
StringBuilder sb;
//新建标记斜线是否被占用的boolean属性
//1.左斜线,y-x从-4到4,那么全部+4就是从0到8
//2.右斜线,x+y从0-8
boolean[] leftDiagonal;
boolean[] rightDiagonal;
boolean[] usedY;
public List<List<String>> solveNQueens(int n) {
if(n ==1){
path.add("Q");
res.add(new ArrayList(path));
return res;
}
leftDiagonal = new boolean[2*n-1];
rightDiagonal = new boolean[2*n-1];
usedY = new boolean[n];
backTracking(n,0);
return res;
}
public void backTracking(int n,int x){
sb = new StringBuilder();
//终止条件,达成目标的条件。
if(path.size() == n){
res.add(new ArrayList(path));
return;
}
for(int y = 0;y<n;y++){//不需要设定x,行是自动下移一位的
//如果当前列已经被占用了
if(usedY[y]){
continue;
}
//如果当前坐标对角线有其他棋子,continue
//1.检查左斜线与右斜线
if(leftDiagonal[y - x + n - 1] || rightDiagonal[ x + y]){
continue;
}
//其他情况,当前坐标可用
//1.记录下当前的棋子字符串表达式,并放入path中
for(int j = 0;j<n;j++){
if(j == y){
sb.append("Q");
}
else{
sb.append(".");
}
}
path.add(sb.toString());
//将当前坐标对应的左右斜线标记为用过的
leftDiagonal[y-x+ n - 1] = true;
rightDiagonal[x+y] = true;
//将当前列标记为使用过的
usedY[y] = true;
//行向下移动
x++;
//进入递归
backTracking(n,x);
//回溯
//重置信息
x--;
usedY[y] = false;
leftDiagonal[y-x+n-1] = false;
rightDiagonal[x+y] = false;
path.removeLast();
}
}
}
看完题解精简一下,我们可以直接使用一个二维char数组来记录最后的输出信息,不用再使用StringBuilder来连接,省去循环步骤。
class Solution {
List<List<String>> res = new ArrayList<>();
LinkedList<String> path = new LinkedList<>();
//新建标记斜线是否被占用的boolean属性
//1.左斜线,y-x从-4到4,那么全部+4就是从0到8
//2.右斜线,x+y从0-8
boolean[] leftDiagonal;
boolean[] rightDiagonal;
boolean[] usedY;
public List<List<String>> solveNQueens(int n) {
char[][] chessBoard = new char[n][n];
//填充默认棋盘(全部为.)
for(char[] c : chessBoard){
Arrays.fill(c,'.');
}
leftDiagonal = new boolean[2*n-1];
rightDiagonal = new boolean[2*n-1];
usedY = new boolean[n];
backTracking(n,0,chessBoard);
return res;
}
public void backTracking(int n,int x,char[][] chessBoard){
//终止条件,达成目标的条件。
if(x == n){//遍历到了最后一行且记录进了结果
res.add(toList(chessBoard));
return;
}
for(int y = 0;y<n;y++){//不需要设定x,行是自动下移一位的
//如果当前列已经被占用了
if(usedY[y]){
continue;
}
//如果当前坐标对角线有其他棋子,continue
//1.检查左斜线与右斜线
if(leftDiagonal[y - x + n - 1] || rightDiagonal[ x + y]){
continue;
}
//其他情况,当前坐标可用
//1.记录下当前的棋子字符串表达式,并放入path中
chessBoard[x][y] = 'Q';
//将当前坐标对应的左右斜线标记为用过的
leftDiagonal[y-x+ n - 1] = true;
rightDiagonal[x+y] = true;
//将当前列标记为使用过的
usedY[y] = true;
//行向下移动
x++;
//进入递归
backTracking(n,x,chessBoard);
//回溯
//重置信息
x--;
usedY[y] = false;
leftDiagonal[y-x+n-1] = false;
rightDiagonal[x+y] = false;
chessBoard[x][y] = '.';
}
}
public List<String> toList(char[][] chessBoard){
List<String> list = new ArrayList<>();
for(char[] c : chessBoard){
list.add(String.copyValueOf(c));
}
return list;
}
}
新用到的API:
Arrays.fill(arrayname,value) 数组填充方法
将指定的数值value填充给每个array元素。
String.copyValueOf(char[] data)
它可以将字符数组的所有字符复制到一个新的字符数组中,并返回一个新的字符串。
该方法与valueOf(char[] data)有点类似,但是valueOf是犯规一个新的字符数组,并不会使用数组中的字符创建新的字符串对象。
该方法的重载: