介绍
N皇后问题是指在N*N的棋盘上摆放N个皇后,要求任何两个皇后不同行、不同列、不在同一条斜线上。给定一个整数n,返回n皇后的摆法有多少种。例如1皇后有1种摆法,2或3皇后无解。
解法
按行遍历递归
由于每一行必定有一个皇后,第i个皇后一定摆放在第i行。我们可以按行遍历,寻找每一行皇后可能的列摆放位置j,由于每一行都有n个可能位置,所以时间复杂度是 O ( n n ) O(n^n) O(nn)。代码如下:
// 按行遍历递归
public int nQueen(int n) {
if (n < 1) {
return 0;
}
// record[i]代表第i个皇后放在了第i行的第几列
int[] record = new int[n];
return process(0, record, n);
}
// row 代表当前进行的是第i行的判断(0到row-1行一定已经按照规则摆好)
// n 代表总行数
// 返回值是合理的摆法总数
public int process(int row, int[] record, int n) {
if (row == n) {
// 基线条件:如果当前所有行已经判断完,则已经得出一种摆法
return 1;
}
int res = 0;
for (int col = 0; col < n; ++col) {
// 判断当前位置是否会和之前的位置冲突
if (isValid(record, row, col)) {
// 有效的摆法,进入下一行
record[row] = col;
res += process(row + 1, record, n);
}
}
return res;
}
// 判断row行的皇后位置与0到row-1行的皇后位置是否有冲突
public boolean isValid(int[] record, int row, int col) {
// 判断方法:列是否相等+是否在同一斜线上(斜率45°)
for (int i = 0; i < row; ++i) {
if (record[i] == col || (row - i) == Math.abs(col - record[i])) {
return false;
}
}
return true;
}
位运算
位运算思路与按行遍历递归一样,但是可以在常数级别上优化运行速度(虽然时间复杂度仍然是 O ( n n ) O(n^n) O(nn),但运行速度更快),代码如下:
// 位运算优化,注意,n不能大于所用数据类型的二进制位数(如int型是32,则n不能大于32)
public int nQueenBit(int n) {
if (n < 1 || n > 32) {
return 0;
}
// 构建用于判定位置的二进制数,n皇后问题就用n个二进制1表示
// 这也是为什么n有限制
int limit = n == 32 ? -1 : ((1 << n) - 1);
return process(limit, 0, 0, 0);
}
// colLim 列的限制,1的位置不能放皇后,0的位置可以放皇后
// leftDiaLim 左斜线的限制,同上
// rightDiaLim 右斜线的限制,同上
public int process(int limit, int colLim, int leftDiaLim, int rightDiaLim) {
if (colLim == limit) {
// 基线条件:与递归方式相同,如果全部摆完,即得到一种摆法
return 1;
}
int pos = 0;
int mostRightOne = 0;
// (colLim | leftDiaLim | rightDiaLim)得到当前行不能摆放皇后的位置,取反则1是可以拜访皇后的位置,与limit & 是限制比较在固定位上进行,防止更高位影响判断
// 例如8皇后问题,limit的9位以上都是0,与limit & 即可排除高位影响
pos = limit & (~(colLim | leftDiaLim | rightDiaLim));
int res = 0;
while (pos != 0) {
// 取pos的最右一位1,即最右的可以放皇后的位置
mostRightOne = pos & (~pos + 1);
// 去除最右1
pos = pos - mostRightOne;
// colLim | mostRightOne,进入下一行,要避开的列多了当前位置,进行或运算即可
// (leftDiaLim | mostRightOne) << 1 进入下一行,前面所有行的左斜线影响都会向左偏移一位,直接或运算再左移即可
// 右斜线同左斜线,注意是无符号右移保证整体右移
res += process(limit, colLim | mostRightOne, (leftDiaLim | mostRightOne) << 1, (rightDiaLim | mostRightOne) >>> 1);
}
}
四皇后问题的运行过程示例: