题目
- n皇后问题研究的是如何将n个皇后放置在n*n的棋盘上。并且使皇后彼此之间不能相互攻击。皇后之间不能在同一行,同一列,正斜,反斜上同时存在
- 上图为 8 皇后问题的一种解法。
- 给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。
- 每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
示例:
输入: 4
输出: [
[".Q…", // 解法 1
“…Q”,
“Q…”,
“…Q.”],
["…Q.", // 解法 2
“Q…”,
“…Q”,
“.Q…”]
]
解释: 4 皇后问题存在两个不同的解法。
解析:回溯搜索
思路分析:
- 我们这里以4皇后为例,他的“搜索”过程如下,大家完全可以在纸上模拟过程:
- 以下假定是给棋盘的每一行从左到右标记为1,2,3,4
- 因为是一行一行进行摆放的,因此这些“皇后”一定不在同一行中,无需额外进行设置状态
- 为了保证其不再同一个列中,即不能出现类似[2,2,2,2]这种只有另两个相同的情况,我们用col的集合来存放记录摆放到棋盘中的哪一列,若再次摆放的时候,当前列已经在该set中,即列重复。
- 副对角线不能重复,其规律就是:行 + 列 = 常数
- 主对角线不能重复,其规律就是:行 - 列 = 常数
- 我们按行存放,若放置成功,则跳到下一行,若不成功,继续下一列,若当前行的列数遍历完毕,还没有找到合适的,进行回溯,将上一个皇后重新放置。重复过程。参考上面的动图
代码如下
- 详细可以参考注释
package com.xiyou.solutio;
import java.util.*;
/**
* @author 92823
* n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
*
*
*
* 上图为 8 皇后问题的一种解法。
*
* 给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。
*
* 每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
*
* 示例:
*
* 输入: 4
* 输出: [
* [".Q..", // 解法 1
* "...Q",
* "Q...",
* "..Q."],
*
* ["..Q.", // 解法 2
* "Q...",
* "...Q",
* ".Q.."]
* ]
* 解释: 4 皇后问题存在两个不同的解法
*
* 横排 竖排 斜着都不能有皇后
*/
public class HuangHou {
public List<List<String>> solveNQueens(int n) {
List<List<String>> res = new ArrayList<>();
if (n == 0) {
// 如果当前传进来的是0行
return res;
}
// 当前数组用来存放每一列的值,不去管行
int[] nums = new int[n];
// 赋值每一列的元素
for (int i = 0; i < n; i++) {
nums[i] = i;
}
// 存放当前哪一列中有值
Set<Integer> col = new HashSet<>();
// 主对角线存值
Set<Integer> master = new HashSet<>();
// 从对角线存值
Set<Integer> slave = new HashSet<>();
// 存放保存的值到stack中
Stack<Integer> stack = new Stack<>();
backtrack(nums, 0, n, col, master, slave, stack, res);
return res;
}
/**
* 棋盘:
* 1 2 3 4
* 1 2 3 4
* 1 2 3 4
* 1 2 3 4
* 递归调用,往棋盘中放皇后
* @param nums 里面存放的是棋子, 值是当前的列
* @param row 当前是第几行
* @param n 棋盘的长度
* @param col 用来判断当前列中是否有重复的
* @param master 用来判断主对角线是否有重复的
* @param slave 用来判断次对角线是否有重复的
* @param stack 当前已经存放进棋盘的棋子
* @param res 最终的返回结果
*/
private void backtrack(int[] nums,
int row,
int n,
Set<Integer> col,
Set<Integer> master,
Set<Integer> slave,
Stack<Integer> stack,
List<List<String>> res) {
// 递归退出的条件,已经遍历完毕了,前几行都放已经处理完了(要么遍历到最后都没有满足条件的)
if (row == n) {
// 直接将当前值进行转换
List<String> board = convert2board(stack, n);
res.add(board);
return;
}
// 针对每一列 尝试是否可以放置
for (int i = 0; i < n; i++) {
// 判断当前列上是否有这个值
// 行就不用判断了,一行只有一个反正
// 判断主对角线是否有值 主对角线上的值 row + i = 常量
// 判断从对角线是否有值 从对角线上的值 row - i = 常量
if (!col.contains(i) && !master.contains(row + i) && !slave.contains(row - i)) {
// 如果当前列和主对角线和从对角线都没有存放这个元素
// 存放当前值
stack.add(nums[i]);
// 存放列,表示当前列有值
col.add(i);
// 存放主对角线,表示当前主对角线有值
master.add(row + i);
// 存放主对角线,表示当前从对角线有值
slave.add(row - i);
// 递归调用
backtrack(nums, row + 1, n, col, master, slave, stack, res);
// 进行回溯
// 从对角线删除上一个元素
slave.remove(row - i);
// 主对角线删除上一个元素
master.remove(row + i);
// 删除列,表示当前列没有值
col.remove(i);
// 栈中的值去除,表示当前栈去除当前值
stack.pop();
}
}
}
/**
* 进行返回值的转换,将stack中的值转换成list
* @param stack
* @param n
* @return
*/
private List<String> convert2board(Stack<Integer> stack, int n) {
List<String> board = new ArrayList<>();
for (Integer num : stack) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < n; i++) {
// 先把每一个的元素赋值为'.'
stringBuilder.append(".");
}
// 替换,将指定位置上的.替换成Q
stringBuilder.replace(num, num+1, "Q");
// 一行的值处理完毕,添加到列表中
board.add(stringBuilder.toString());
}
return board;
}
public static void main(String[] args) {
HuangHou huangHou = new HuangHou();
huangHou.solveNQueens(4);
}
}