按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
该解法基本为傻瓜式解法,还有许多可以优化的地方,现代码如下,附上注释
-
class Solution { public List<List<String>> solveNQueens(int n) { List<List<String>> res=new ArrayList<List<String>>(); //新建棋盘 char[][] jieguo=new char[n][n]; //复制一个棋盘,保证行无queen boolean[][] line=new boolean[n][n]; //Set集合,保证不会有重复的列被使用 Set<Integer> columns = new HashSet<Integer>(); //初始化棋盘 for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ jieguo[i][j]='.'; } } dfs(jieguo,n,line,res,0,columns); return res; } public void dfs(char[][] jieguo,int n,boolean[][] line,List<List<String>> res,int indexL,Set<Integer> columns ){ //回溯的结束条件 if(n==0){ //将jieguo数组转换为List<String> List<String> ans=zhuanhuan(jieguo); //加入到集合中去 res.add(ans); return; } //保证行不越界 if(indexL>=jieguo.length||indexL<0){ return; } //标志位,保证交叉方向无queen boolean isTrue=false; //对每一列试探性填入queen for(int indexC=0;indexC<jieguo.length;indexC++){ //保证目前位置行列方向无queen if(!Arrays.asList(line[indexL]).contains(true)){ //保证当前列无queen if(columns.contains(indexC)){ continue; } //保证交叉方向无queen int indexLL=indexL; int indexLL2=indexL; int indexCC=indexC; int indexCC2=indexC; //交叉的右上方向 if(indexLL!=0&&indexCC!=0){ while(indexLL!=0&&indexCC!=0){ indexLL--; indexCC--; } } //遍历右上方向是否有queen for(int i=indexCC;i<jieguo.length&&indexLL<jieguo.length;i++,indexLL++){ if(jieguo[indexLL][i]=='Q'){ isTrue=true; } } //交叉的右下方向 if(indexLL2!=0&&indexCC2!=jieguo.length-1){ while(indexCC2!=jieguo.length-1&&indexLL2!=0){ indexLL2--; indexCC2++; } } //遍历左上方向是否有queen for(int j=indexCC2;j>=0&&indexLL2<jieguo.length;j--,indexLL2++){ if(jieguo[indexLL2][j]=='Q'){ isTrue=true; } } //标志位为false,该位置可以填入 if(isTrue!=true){ //在棋盘当前位置填入queen jieguo[indexL][indexC]='Q'; //将该位置置为true,保证下一次该行不会填入queen line[indexL][indexC]=true; //将该列加入到Set集合中去 columns.add(indexC); //对下一行进行回溯 dfs(jieguo,n-1,line,res,indexL+1,columns); } } //回溯,保证下一个位置的插入 jieguo[indexL][indexC]='.'; //恢复棋盘的行状态 line[indexL][indexC]=false; //恢复棋盘的列状态 columns.remove(indexC); //恢复标志位 isTrue=false; } } public List<String> zhuanhuan(char[][] jieguo){ List<String> ans=new ArrayList<String>(); for(int i=0;i<jieguo.length;i++){ String bb=new String(jieguo[i]); ans.add(bb); } return ans; } }
反思:
- 对于数组的列状态的判断在一开始想用空间换时间的做法,最后发现想法错误,交换数组的下标无法判断数组的列状态
- 在恢复棋盘状态时,忘记将标志位也恢复,导致找了很长时间的逻辑错误,在leetcode上找逻辑错误是真的难
- 虽然结果都通过了,但是速度比较慢,还有很多优化的地方,代码比较冗余
优化:
- 可以将标志位isTrue去掉,直接使用continue代替
- 可以将line数组去掉,判断行有无queen时,直接使用jieguo数组直接判断
- 对于交叉方向有无queen的判断可以再次进行优化,这里使用了太多的变量保存行列的坐标