问题
N皇后问题是典型的回溯法问题,大学算法课上一般都会讲。问题大致意思如下,国际象棋中有一个棋子叫皇后,皇后可以攻击和她同处一行、一列、对角线的其它棋子,加入给定一个棋盘N*N,如何摆放N个皇后使得她们之间不能相互攻击。
LeetCode 51需要返回所有可能的棋子摆放,而52只需要返回有多少种可能即可。
解析
大框架知道了,前面的文章已经介绍了回溯法的基本框架:
public void backtrack(List<Integer> list, int i, int n){
if(i==n){
//满足条件,是一种可能的情况,添加到集合中
}
for(int i=0;i<n;i++){
//剪枝,考虑满足要求的下一次回溯
//回溯
backtrack(list,i+1,n);
//返回到上一步
}
}
只是每次的剪枝不一样,这里该如何剪枝呢?首先,列不能重复,那么我们可以创建一个set判断是否有重复的列。这里的关键是如何判断对角线。
注意到对角线都是经过当前位置且斜率为+1或者-1的直线,那么只要确定截距即可确定这条直线。 因此我们新建两个set,保存两个对角线的截距即可确定是否会产生冲突。
代码
LeetCode 51:
class Solution {
Set<Integer> dia1;//记录对角线
Set<Integer> dia2;//记录第二个对角线
Set<Integer> col;//记录列
List<List<Integer>> ans;//返回棋子处在列号的所有可能
List<List<String>> reuslt;//所有符合要求的结果
public List<List<String>> solveNQueens(int n) {
dia1 = new HashSet();
dia2 = new HashSet();
col = new HashSet();
ans = new ArrayList();
backtrack(new ArrayList(),0,n);
reuslt = construtBoard(ans,n);
return reuslt;
}
//回溯法,获得列号
public void backtrack(List<Integer> arr, int i,int n){
if(i==n){
ans.add(new ArrayList(arr));
}
for(int j=0;j<n;j++){
if(col.contains(j)){
continue;
}
//将棋盘看成成坐标系,那么对角线均为斜率为+1或者-1,只是截距不同,因此我们每次保存正负对角线的截距即可
int diac1 = i - j;
int diac2 = i + j;
if(dia1.contains(diac1)||dia2.contains(diac2)){
continue;
}
arr.add(j);
col.add(j);
dia1.add(diac1);
dia2.add(diac2);
backtrack(arr, i+1, n);
col.remove(j);
dia1.remove(diac1);
dia2.remove(diac2);
arr.remove(arr.size()-1);
}
}
//根据列号构造棋盘
public List<List<String>> construtBoard(List<List<Integer>> ans, int n){
List<List<String>> temp = new ArrayList();
for(int i=0;i<ans.size();i++){
List<String> te1 = new ArrayList();
for(int j=0;j<n;j++){
char ch[] = new char[n];
Arrays.fill(ch, '.');
ch[ans.get(i).get(j)] = 'Q';
te1.add(String.valueOf(ch));
}
temp.add(te1);
}
return temp;
}
}
类似的,可以得到52题的代码:
LeetCode 52:
class Solution {
int ans = 0;
Set<Integer> dia1;
Set<Integer> dia2;
Set<Integer> col;
public int totalNQueens(int n) {
dia1 = new HashSet();
dia2 = new HashSet();
col = new HashSet();
backtrack(0,n);
return ans;
}
public void backtrack(int i,int n){
if(i==n){
ans++;
}
for(int j=0;j<n;j++){
if(col.contains(j)){
continue;
}
int diac1 = i - j;
int diac2 = i + j;
if(dia1.contains(diac1)||dia2.contains(diac2)){
continue;
}
col.add(j);
dia1.add(diac1);
dia2.add(diac2);
backtrack(i+1, n);
col.remove(j);
dia1.remove(diac1);
dia2.remove(diac2);
}
}
}