今天在刷LeetCode时,遇到了一个N皇后的问题,问题如下:
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击,即同一横行、数列、斜线不能出现两个皇后。需要输出所有的摆放情况。输出格式为:每个方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
这是一个非常经典的应用回溯算法的题目,看了网上一些的解答,空间复杂度大多都是O(n^2),下面是我的解法,使得空间复杂度降低为O(n):
为了解决这个问题,我们建立了三个数组row[n],first[2n-1],second[2n-1](三个数组取值,只有0或1)以及变量flag。flag是表示当前我们要在棋盘上放置第flag行的皇后,
row[i]=0 表示第i列没有放置皇后
row[i]=1 表示第i列已放置皇后
first[i-flag+n-1]=0 表示(flag,i)坐标上的主对角线没有放置皇后
first[i-flag+n-1]=1 表示(flag,i)坐标上的主对角线已放置皇后
second[i+flag]==0 表示(flag,i)坐标上的副对角线没有放置皇后
second[i+flag]==1 表示(flag,i)坐标上的副对角线已放置皇后
(主对角线:左上到右下;副对角线:右上到坐下)
这个地方只需要O(n)空间复杂度就可以来判别(flag,i)处是否可以放置皇后,时间消耗也会大大降低,理解了上面的这个步骤就很简单了,剩下的就是回溯算法的应用了。
//插入某一行的输出格式
public static String s(int i,int n) {
String string="";
for (int j = 0; j < n; j++) {
if (j==i) {
string+="Q";
}else {
string+=".";
}
}
return string;
}
public static void Queens(int n,List<List<String>> result,List<String> list,int []row,int []first,int []second,int flag) {
if (flag==n) {
//此时,最后一行也追加到了list中,则可以把list存储的方案追加到最终结果
result.add(new ArrayList<String>(list));
}else {
for (int i = 0; i < n; i++) {
if (row[i]==0&&first[i-flag+n-1]==0&&second[i+flag]==0) {
//如果该处可以放置皇后
row[i]=1;
first[i-flag+n-1]=1;
second[i+flag]=1;
flag++;
//把该行的格式追加到list中
list.add(s(i, n));
//开始下一行的放置皇后
Queens(n, result, list, row, first, second, flag);
//撤销本次的放置
row[i]=0;
flag--;
first[i-flag+n-1]=0;
second[i+flag]=0;
list.remove(list.size()-1);
}
}
}
}
public static List<List<String>> solveNQueens(int n) {
List<List<String>> result=new ArrayList<List<String>>();
int []row=new int[n];
int []first=new int[n*2-1];
int []second=new int[n*2-1];
List<String> list=new ArrayList<String>();
Queens(n, result, list, row, first, second,0);
return result;
}
测试代码如下:
public static void main(String[] args) {
// TODO Auto-generated method stub
//n为输入的皇后个数
int n=4;
List<List<String>> result=solveNQueens(n);
for (int i = 0; i < result.size(); i++) {
for (int j = 0; j < result.get(i).size(); j++) {
System.out.println(result.get(i).get(j));
}
System.out.println("");
}
}
输出结果如下:
.Q…
…Q
Q…
…Q.
…Q.
Q…
…Q
.Q…