一 题目
The n-queens puzzle is the problem of placing n queens on an n×n chessboard such that no two queens attack each other.
Given an integer n, return all distinct solutions to the n-queens puzzle.
Each solution contains a distinct board configuration of the n-queens' placement, where 'Q'
and '.'
both indicate a queen and an empty space respectively.
Example:
Input: 4
Output: [
[".Q..", // Solution 1
"...Q",
"Q...",
"..Q."],
["..Q.", // Solution 2
"Q...",
"...Q",
".Q.."]
]
Explanation: There exist two distinct solutions to the 4-queens puzzle as shown above.
Accepted 159,582 Submissions 384,320
二 分析
hard 级别,N皇后问题。我记得上学的时候学过8皇后的问题,不过早都忘了,应该是经典的算法问题。
国际象棋的皇后的攻击规则:就是行、列、对角线(正反)。不能有重复的,否则就会攻击到。上面说了限制规则,目标就是在N*N的棋盘上放上N个皇后,互相不攻击。
这个题目,跟之前的数独问题: leetcode 37. Sudoku Solver 从思路上很类似的。就是没有想到直观的解决办法,要去不断的尝试,数独是我在一个位置尝试填入一个数,满足我就填下一个,不满足我尝试别的数。这个就是我尝试在一个位置放入皇后,满足方一个,不满足把原来放入皇后移走。尝试下一个位置。适合递归的思路,深度优先。完成就加入到结果。
依然没有做出来,后来看了网上大神的文章,才理清思路。
1 算法开始, 初始化N*N棋盘,填入“.”占位。
2. 从0行开始调用递归函数。
3 递归函数:
3.1我们首先判断当前行数是否已经为n,是的话说明所有的皇后都已经成功放置好了,所以要把 queens 数组加入结果 res 中即可。
3.2 否则 我们遍历该行的所有列的位置,行跟列的位置都确定后, 在当前行,当前列的位置上判断是否满足条件(即保证经过这一点的行,列与斜线上都没有两个皇后)
如果满足:先标识位Q(放入皇后),在递归下一行。
不满足条件的情形:
- 将当前行当前列的位置回溯,置为未放状态,再接着判断当前行下一列,目的为了遍历所有的解决方案。
其中校验函数需要对比画图看。因为是逐行去填充而每行只有一个皇后,且在数组中只占据一个元素的位置,行冲突就不存在了。只看剩余的列冲突、正反对角线冲突就好。这里要注意找规律。代码里还有个剪枝优化,就是不用判断一整个线的数据,值从当前的queens[i,j]开始向上找,减少了判断。
还是看代码直观些。
//n皇后问题。
//规则:横、竖、斜线上不能有重复的。
public static void main(String[] args) {
// TODO Auto-generated method stub
List<List<String>> res =solveNQueens(4);
System.out.println(JSON.toJSON( res));
}
//递归
public static List<List<String>> solveNQueens(int n) {
List<List<String>> res = new ArrayList();
//初始化
char[][] queens= new char[n][n];
//填充
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
queens[i][j]='.';
}
}
helper(0,queens,res);
return res;
}
private static void helper(int row, char[][] queens, List<List<String>> res) {
//符合条件,放满了
if(row== queens.length ){
List<String> ls=new ArrayList<String>();
for(int i=0;i<queens.length;i++){
ls.add(String.valueOf(queens[i]));
}
res.add(ls);
return;
}
//遍历所有列
for(int col=0;col<queens.length;col++ ){
//check:是否可在row行j列处放Q
if(isValid(queens ,row,col)){
//先标记
queens[row][col]='Q';
//尝试下一行
helper(row+1,queens, res);
//不匹配再清除
queens[row][col]='.';
}
}
}
//rule:限制规则:(按行处理的,所以不在检查行)
static boolean isValid( char[][] queens, int row,int col ){
//check 列
for(int i=0;i<row;i++){
if(queens[i][col]=='Q'){
return false;
}
}
//check 斜线/:
for(int i=row-1,j=col+1;i>=0&&j<queens.length;i--,j++){
if(queens[i][j]=='Q'){
return false;
}
}
//check 反斜线\:只查到上半截
for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
if (queens[i][j]=='Q') {
return false;
}
}
return true;
}
Runtime: 2 ms, faster than 95.36% of Java online submissions for N-Queens.
Memory Usage: 37.9 MB, less than 75.68% of Java online submissions for N-Queens.
时间复杂度O(N!).
算法珠玑上还介绍一种只使用一个一维数组 queenCol 来保存所有皇后的列位置。这个很牛逼,因为二维数组是更接近我们正常思维的,压缩到一维数组,整个大逻辑不变的情况下,匹配成功的情况下,加入到结果集需要转换下,校验规则都要变。这里大神很厉害,判断两个点是否在对角线上,用了二点的横坐标差的绝对值等于纵坐标差的绝对值,666.大神总是碾压式存在。
class Solution {
public List<List<String>> solveNQueens(int n) {
List<List<String>> res = new ArrayList();
int[] queens = new int[n];
//初始化-1
for(int i=0;i<n;i++){
queens[i] =-1;
}
helper(0,queens,res);
return res;
}
private static void helper(int row, int[] queens, List<List<String>> res) {
//符合条件,放满了
if(row== queens.length ){
List<String> ls = new ArrayList();
//转换为结果
for(int i=0;i<queens.length;i++ ){
char[] charArray = new char[queens.length];
for(int j=0;j<queens.length;j++){
if(j==queens[i]){
charArray[j] ='Q';
}else{
charArray[j] ='.';
}
}
ls.add(String.valueOf(charArray));
}
res.add(ls);
}
//遍历所有列
for(int col=0;col<queens.length;col++ ){
//check:是否可在row行j列处放Q,不满足就尝试下一列
if(isValid(queens ,row,col)){
queens[row]= col;
helper(row+1,queens,res );
queens[row]= -1;
}
}
}
//判断当前位置能否放Q
static boolean isValid(int[] queens, int row,int col ){
for(int i=0;i<row;i++){
//check 列
if(queens[i]==col){
return false;
}
//判断对角线:横坐标差的绝对值等于纵坐标差的绝对值
if (Math.abs(i - row) == Math.abs(queens[i] - col)) return false;
}
return true;
}
}
Runtime: 2 ms, faster than 95.36% of Java online submissions for N-Queens.
Memory Usage: 36.2 MB, less than 100.00% of Java online submissions for N-Queens.
理论上会更快,我觉得实际跑出来结果一样。我觉得是受累于拼装结果那个字符串了。
参考:
https://soulmachine.gitbooks.io/algorithm-essentials/java/dfs/n-queens.html