题目
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
上图为 8 皇后问题的一种解法。
给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。
每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
示例:
输入: 4
输出: 2
解释: 4 皇后问题存在如下两个不同的解法。
[
[".Q…", // 解法 1
“…Q”,
“Q…”,
“…Q.”],
["…Q.", // 解法 2
“Q…”,
“…Q”,
“.Q…”]
]
算法1
//回溯递归方法解决
public class P52_NQueensII {
int count = 0;
public int totalNQueens(int n) {
helper(new ArrayList<String>(), 0, new boolean[n], new boolean[2*n], new boolean[2*n], n);
return count;
}
private void helper(List<String> board, int row, boolean[] cols, boolean[] d1, boolean[] d2, int n){
if (row == n) {
count++;
return;
}
for (int col=0; col<n; col++){
int id1 = col - row + n;
int id2 = col + row;
if (!cols[col] && !d1[id1] && !d2[id2]){
char[] r = new char[n];
Arrays.fill(r, '.');
r[col] = 'Q';
board.add(new String(r));
cols[col] = true;
d1[id1] = true;
d2[id2] = true;
helper(board, row+1, cols, d1, d2, n);
// 还原过程
board.remove(board.size()-1);
cols[col] = false;
d1[id1] = false;
d2[id2] = false;
}
}
}
}
思路:基本的回溯思想,详见leetcode习题集——51. N皇后
算法2(位运算法,目前最高效)
//深度优先方法解决
//位运算解决
public class P52_NQueensII2 {
private int count = 0;
public int totalNQueens(int n) {
if(n==1) return 1;
DFS(0, 0, 0, 0, n);
return count;
}
public void DFS(int row, int col, int pie, int na, int n) {
if (row >= n) {
count++;
return;
}
int a = (~(col | pie | na));
int b = ((1 << n) - 1);
int bits = a & b;
while (bits > 0) {
int P = bits & (-bits);
DFS(row + 1, col | P, (P | pie) << 1, (na | P) >> 1, n);
bits = bits & (bits - 1);
}
}
}
代码解析:
row
代表当前处理的行号,本算法根据行号进行遍历,保证一行放置一个皇后,不产生冲突col
、pie
、na
分别代表了列、左斜对角线、右写对角线上的位置禁用情况bit
代表为计算之后当前行的禁用情况,1
代表可以放置,0
代表禁用行int P = bits & (-bits);
该等式等价于int P = bits & (~bits+1)
,意为取bit上为1
的最低位DFS(row + 1, col | P, (P | pie) << 1, (na | P) >> 1, n)
5.1row+1
代表处理下一行
5.2col | P
表示将当前列设为禁用
5.3(P | pie) << 1
表示将当前位置的左边一位设为禁用(即左下,左对角线)
5.4(na | P) >> 1
表示将当前位置的右边以为设为禁用(即左下,左对角线)bits = bits & (bits - 1)
,表示将最低位的1置为0,表示该位已经遍历过了
以下以n=4
为例观察一下算法运行的步骤
- 初始情况计算bits
bits=a&b=1111(2进制)
- 取最低位为1的P,放置皇后
P=bits&(~bits+1)=0001(2进制)
- 进入下一层递归(下一行),同时修正禁用位置
col=0001(2进制)
pie=0010(2进制)
na=0000(2进制)
a=~(0001|0010|0000)=1100
bits=a&b=1100&1111=1100
可以看出,上一行中的同列和斜对角都被禁用了- 取最低位为1的P,放置皇后
- 进入下一层递归(下一行),同时修正禁用位置
col=0001|0100=0101(2进制)
pie=(0010|0100)<<1=1100(2进制)
na=(0000|0100)>>1=0010(2进制)
a=~(0101|1100|0010)=0000
bits=a&b=0000&1111=0000
由此可知,本行所有位置都被禁用掉了, bits=0
,所以本层递归结束
- 回到上一行的
bits = bits & (bits - 1)
执行bits = bits & (bits - 1)
前bits值为1100
bits=bits&(bits-1)=1100&1011=1000
此步骤的意义是将最低位的1置为0,即标记皇后已经在这里放置过,如果bits>0,则需要继续向高位遍历。- 继续向高位遍历
继续向高位遍历的后续重复步骤3~6即可