Follow up for N-Queens problem.
Now, instead outputting board configurations, return the total number of distinct solutions.
思路1:打表
public class Solution {
public int totalNQueens(int n) {
int[] result = new int[] { 0, 1, 0, 0, 2, 10, 4, 40, 92, 352, 724,
2680, 14200, 73712, 365596, 2279184, 14772512, 95815104,
666090624 };
return result[n];
}
}
思路2:使用回溯法,参照
http://blog.csdn.net/mlweixiao/article/details/40984541
public class Solution {
public static int num=0;
private boolean isValid(List<Integer> al) {
int column = al.get(al.size() - 1);
for (int i = 0; i < al.size() - 1; i++) {
if (column == al.get(i)
|| Math.abs(column - al.get(i)) == Math.abs(al.size()-1 - i)) {
return false;
}
}
return true;
}
private void search(int n, List<Integer> al, int col) {
if (col > n){
num++;
}else {
for (int j = 0; j < n; j++) {
al.add(j);
if (isValid(al)) {
search(n, al, col + 1);
}
al.remove(al.size()-1);
}
}
}
public int totalNQueens(int n) {
num=0;
search(n,new LinkedList<Integer>(),1);
return num;
}
}
思路3 :回溯法,只不过使用位移,速度超快,参考
http://blog.csdn.net/kai_wei_zhang/article/details/8033194
假设棋盘的前i-1行已经被填充,现在我们需要填充第i行,那怎么得出第i行哪些是可以填充呢?那么有三种情况是禁止,同一列,斜率k为1的斜线,斜率k为-1的斜线。要是我们能用三个变量row,ld,rd来分别存储这三种情况的禁止位该多好啊。下面是是六皇后问题的,假设前三行已经填充了,我们需要填充第四行
其中row,ld,rd的二进制表示如上,1代表禁止位,0代表空位(可以放皇后)。对于第四行而言,那么第1,3,5列(从左往右数)不能放,故row的二进制表示为101010;
对于第四行而言,斜率k为1的斜线有两条对其有影响,最左上的一条对第四行没有影响,k=1的中间一条斜线对第四行的影响是第一个位置(从左往右数)不能放,k=1的右下一条斜线对第四行的影响是第4个位置(从左往右数)不能放,故k=1的斜线对第四行的影响为100100(从左往右数);而k=-1的三条斜线对第四行的影响分别是第4,5,6个位置(从左往右数)不能放,故rd的二进制表示为000111(从左往右数),那么这三个数row,rd,ld二进制取‘或’就可以得到101111,101111对于第四行而言就意味着只有第二个位置能放。那么第四行填充了,这三个数row,rd,ld该怎么变化呢?row与刚填充的第二个位置(用010000表示)取或即可,rd与刚填充的第二个位置(用010000表示)取或,还要向右移1位,因为其斜率为-1,第四行的禁止位相对于第五行而言是左上。同理,ld与刚填充的第二个位置(用010000表示)取或,还要向左移1位。当row的二进制的所有位全为1时,则表示填充完成。
public class Solution {
// sum用来记录皇后放置成功的不同布局数;upperlim用来标记所有列都已经放置好了皇后。
static int counter = 0;
static long upperlim = 1;
// 试探算法从最右边的列开始,函数带三个参数row、ld和rd,分别表示在纵列和两个对角线方向的限制条件下这一行的哪些地方不能放
void search(long row, long ld, long rd) {
if (row != upperlim) {
// row,ld,rd进行“或”运算,求得所有可以放置皇后的列,对应位为0,
// 然后再取反后“与”上全1的数,来求得当前所有可以放置皇后的位置,对应列改为1
// 也就是求取当前哪些列可以放置皇后
long pos = upperlim & ~(row | ld | rd);
while (pos != 0) { // 0 -- 皇后没有地方可放,回溯
// 拷贝pos最右边为1的bit,其余bit置0
// 也就是取得可以放皇后的最右边的列
long p = pos & -pos;//相当于p = pos & (~pos + 1)
// 将pos最右边为1的bit清零
// 也就是为获取下一次的最右可用列使用做准备,
// 程序将来会回溯到这个位置继续试探
pos -= p;
// row + p,将当前列置1,表示记录这次皇后放置的列。
// (ld + p) << 1,标记当前皇后左边相邻的列不允许下一个皇后放置。
// (ld + p) >> 1,标记当前皇后右边相邻的列不允许下一个皇后放置。
// 此处的移位操作实际上是记录对角线上的限制,只是因为问题都化归
// 到一行网格上来解决,所以表示为列的限制就可以了。显然,随着移位
// 在每次选择列之前进行,原来N×N网格中某个已放置的皇后针对其对角线
// 上产生的限制都被记录下来了
search(row + p, (ld + p) << 1, (rd + p) >> 1);
}
} else {
// row的所有位都为1,即找到了一个成功的布局,回溯
counter++;
}
}
public int totalNQueens(int n) {
counter = 0;
upperlim = (1 << n) - 1;
test(0, 0, 0);
return counter;
}
}