问题描述:
- N皇后问题是指在N*N的棋盘.上要摆N个皇后,要求任何两个皇后不同行、不同列,也不在同一条斜线上。
- 给定一个整数n,返回n皇后的摆法有多少种。
n=1,返回1。
n=2或3,2皇后和3皇后问题无论怎么摆都不行,返回0。
n=8,返回92。
解题思路:
- 没有什么比较好的解法,只能通过枚举。
- 第一行,首先放在一号位置,然后看第二行;第二行放在第一号位置,如果不与之前放过的皇后冲突(同行、同列、同斜线),则看第三行……
- 类似于深度优先遍历
/**
* N皇后问题,有多少摆法
*/
public static int solveNum1(int n) {
if (n < 1) {
return 0;
}
// record[i], 第i行的皇后被放在了第几列
int[] record = new int[n];
return process1(0, record, n);
}
/**
* @param i 目前来到了第i行
* @param record record[i...i-1]表示之前的行,放了皇后的位置
* @param n 代表整体一共多少行
* @return 返回值是,摆完所有的皇后,合理的摆法有多少种
*/
public static int process1(int i, int[] record, int n) {
if (i == n) {
// 来到了终止行,说明该摆法可行
return 1;
}
int res = 0;
// 尝试i行所有列是否存在可行的摆法
for (int j = 0; j < n; j++) {
// 判断当前i行的皇后,放在第j列会不会和之前0~i-1行的皇后,共列或者共斜线
if (isValid(record, i, j)) {
record[i] = j;
res += process1(i + 1, record, n);
}
}
return res;
}
public static boolean isValid(int[] record, int i, int j) {
for (int k = 0; k < i; k++) {
if (record[k] == j || Math.abs(record[k] - j) == i - k) {
return false;
}
}
return true;
}
位运算优化
- 与上面的枚举思想一致,时间复杂度也一致,只不过使用位运算进行加速
- 将整个棋盘表示成一个二进制数,比如n=8的棋盘,二进制低位就是
11111111
,同时这也限制了n最大只能取32(使用int类型表示,更大可以用long) - 每个过程有四个变量:
limit
:一个n位掩码,n为多少,二进制低位就有多少个1colLim
:列限制,二进制n位上,如果之前某位对应的列放置了皇后,那么该位为1leftDiaLim
:左斜线的限制,1的位置不能放皇后,0的位置可以rightDiaLim
:右斜线的限制,1的位置不能放皇后,0的位置可以
- 各个变量如何随行的变化而变化(假设n=8)?
limit
:永远不变colLim
:如果当前行在第2
列放了皇后,则colLim|01000000
leftDiaLim
:如果当前行在第2
列放了皇后,则leftDiaLim|01000000
后左移一位rightDiaLim
:如果当前行在第2
列放了皇后,则rightDiaLim|01000000
后右移一位
/**
* 位运算优化后的方法,因为使用的是int类型,所以无法处理32皇后以上的问题
*/
public static int solveNum2(int n) {
if (n < 1 || n > 32) {
return 0;
}
// n 为多少,limit的二进制就取多少个1,比如n=8,则limit_2=11111111
int limit = n == 32? -1:(1 << n) - 1;
return process2(limit, 0, 0, 0);
}
/**
* @param limit 一个n位掩码,n为多少,二进制低位就有多少个1
* @param colLim 列限制,二进制n位上,如果之前某位对应的列放置了皇后,那么该位为1
* @param leftDiaLim 左斜线的限制,1的位置不能放皇后,0的位置可以
* @param rightDiaLim 右斜线的限制,1的位置不能放皇后,0的位置可以
* @return 当前条件下,可以成功放置所有皇后的情况数
*/
public static int process2(int limit, int colLim, int leftDiaLim, int rightDiaLim) {
if (colLim == limit) {
// 已经放完了皇后
return 1;
}
int pos = 0;
int mostRightOne = 0;
// 将可以放置皇后的位设为1,不可以的设为0
pos = limit & (~(colLim | leftDiaLim | rightDiaLim));
int res = 0;
while (pos != 0) {
mostRightOne = pos & (~pos + 1);
pos = pos - mostRightOne;
res += process2(limit, colLim | mostRightOne,
(leftDiaLim | mostRightOne) << 1,
(rightDiaLim | mostRightOne) >>> 1);
}
return res;
}
实战练习
LeetCode原题:51. N 皇后 - 力扣(LeetCode)
难度:Hard