这是我参与8月更文挑战的第18天,活动详情查看:8月更文挑战
前言
力扣第五十一题 N 皇后
如下所示:
n 皇后问题 研究的是如何将 n
个皇后放置在 n×n
的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n
,返回所有不同的 **n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q'
和 '.'
分别代表了皇后和空位。
示例 1:
输入: n = 4
输出: [[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释: 如上图所示,4 皇后问题存在两个不同的解法。
示例 2:
输入: n = 1
输出: [["Q"]]
提示:
1 <= n <= 9
- 皇后彼此不能相互攻击,也就是说:任何两个皇后都不能处于同一条横行、纵行或斜线上。
一、思路
N 皇后
是一道非常经典的 回溯算法
题目,值得一做!
tips:这一题和
力扣第三十七题-解数独
非常的像,思路几乎是一样的,有兴趣的也可以看一下那题。
题目中有三个非常重要的信息:
皇后
彼此不能在同一行皇后
彼此不能在同一列皇后
彼此不能在同一斜线
大致的思路分为以下几个部分:
- 使用三个二维数组存储已选择的点在行列及斜线的占用情况
- 因为每一行都会有一个
皇后
,所以当前递归中只遍历当前行的元素(剪枝)
图解算法
在实现的过程中,踩坑最多的地方就是存储斜线的占用情况了,所以下面会主要介绍是如何存储行、列及斜线的占用情况的。
至于递归的话,思路不是很难,精简后的伪代码如下所示:
public void dfs(List<List<String>> ret, int count) {
if (count == len) {
添加结果到ret中
return;
}
// 每一行都会有一个皇后,所以只需遍历n次
for (int i=0; i<len; i++) {
// 判断是否可以放置皇后
if (!rowFlags[count][i] && !colFlags[count][i] && slashFlags[count][i] == 0) {
更新占用情况
// 向下递归
dfs(ret, count+1);
恢复占位情况
}
}
}
变量说明:
boolean[][] positions; // 选择的元素
boolean[][] rowFlags; // 行占用情况
boolean[][] colFlags; // 列占用情况
int[][] slashFlags; // 斜线占用情况
如下图所示,当选择 第一行第二列
作为第一个元素时,即positions[0][1] = true
行、列占用情况
行和列占用可以使用两个 二维布尔数组
来存储
此时存储情况为 rowFlags[0][i]
均为 true
,colFlags[i][1]
均为 true
斜线占用情况
我开始也是用
二维布尔数组
来存储斜线占用情况的,但是我发现会有相互干扰的情况如下图所示,
[3, 4]
与[4, 1]
斜线占用情况会有重合的情况,当向上回溯时就会将重合的地方置为false黄色表示选择的元素,浅色表示占用的斜线情况
所以斜线占用可以试用 二维int数组
来存储,占用就 +1
,回溯时就 -1
此时存储情况为 slashFlags[1][0]
、slashFlags[0][1]
、slashFlags[1][2]
、slashFlags[2][3]
、slashFlags[3][4]
均为 1
二、实现
实现代码
实现代码与思路中保持一致(就是代码有点长了tnt~)
boolean[][] positions;
boolean[][] rowFlags; // 行
boolean[][] colFlags; // 列
int[][] slashFlags; // 斜线
int len;
/**
* 需要分别存储行、列、斜线的状态,不然会相互影响
* @param n
* @return
*/
public List<List<String>> solveNQueens(int n) {
List<List<String>> ret = new ArrayList<>();
rowFlags = new boolean[n][n];
colFlags = new boolean[n][n];
slashFlags = new int[n][n];
positions = new boolean[n][n];
len = n;
dfs(ret, 0);
return ret;
}
/**
* 递归
*/
public void dfs(List<List<String>> ret, int count) {
if (count == len) {
List<String> list = new ArrayList<>();
// 记录结果
for (int i=0; i<len; i++) {
StringBuilder sb = new StringBuilder();
for (int j=0; j<len; j++) {
if (positions[i][j])
sb.append("Q");
else
sb.append(".");
}
list.add(sb.toString());
}
ret.add(list);
return;
}
// 每一行都会有一个皇后,所以只需遍历n次
for (int i=0; i<len; i++) {
// 判断当前列是否可以放置皇后
if (!rowFlags[count][i] && !colFlags[count][i] && slashFlags[count][i] == 0) {
// 更新
positions[count][i] = true;
updateFlags(count, i, true);
// 向下递归
dfs(ret, count+1);
positions[count][i] = false;
updateFlags(count, i, false);
}
}
}
public void updateFlags(int row, int col, boolean flag) {
// 更新行
for (int i=0; i< len; i++) {
rowFlags[row][i] = flag;
}
// 更新列
for (int i=0; i<len; i++) {
colFlags[i][col] = flag;
}
// 更新斜线
int tRow = row; // 起始点(斜向下)
int tCol = col;
while (tRow>0 && tCol >0) {
tRow--;
tCol--;
}
// 更新斜向下
while (tRow<len && tCol<len) {
if (flag) {
++slashFlags[tRow][tCol];
} else if (slashFlags[tRow][tCol] > 0){
--slashFlags[tRow][tCol];
}
tRow++;
tCol++;
}
tRow = row; // 起始点(斜向上)
tCol = col;
while (tRow>0 && tCol<len-1) {
tRow--;
tCol++;
}
// 更新斜向上
while (tRow<len && tCol>-1) {
if (flag) {
++slashFlags[tRow][tCol];
} else if (slashFlags[tRow][tCol] > 0){
--slashFlags[tRow][tCol];
}
tRow++;
tCol--;
}
}
测试代码
public static void main(String[] args) {
new Number51().solveNQueens(8);
}
结果
三、总结
下一题是 N 皇后 II
,题目基本是一样的,会再将本文的思路优化一下。
感谢看到最后,非常荣幸能够帮助到你~♥