LeetCode 51 & 51:N-Queens 问题

题目内容

题目链接N皇后 & N皇后 II
GitHub 导航https://github.com/Avicii4/LeetCode/tree/master/problems/Math/N-Queens

n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。如下图。

在国际象棋中,皇后可以说是移动最“自由”的棋子了,若无阻挡,她可以到达同一行、同一列或同一斜线方向上的任何一个位置。所以,题目中要求的“不能相互攻击”就是要让这些棋子不能同处一行、一列或一斜线上
“N皇后”要求给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 'Q''.' 分别代表了皇后和空位。
而“N皇后 II”题目给定一个整数 n,返回 n 皇后不同的解决方案的数量。
所以两道问题的设定没有变,可以一起解决。

思路分析

这道题的基本思路是回溯法,这也是在 LeetCode 中比较常用的算法了。接下来描述一下本题的思路:

  1. 算法开始, 清空棋盘,当前行设为第一行,当前列设为第一列;
  2. 在当前行,当前列的位置上判断是否满足条件,若不满足,跳到第4步;
  3. 在当前位置上满足条件的情形:
    (1) 在当前位置放一个皇后,若当前行是最后一行,记录一个解;
    (2) 若当前行不是最后一行,当前行设为下一行, 当前列设为当前行的第一个待测位置;
    (3) 若当前行是最后一行,当前列不是最后一列,当前列设为下一列;
    (4) 若当前行是最后一行,当前列是最后一列,回溯,即清空当前行及以下各行的棋盘,然后,当前行设为上一行,当前列设为当前行的下一个待测位置;
    (5)以上返回到第2步;
  4. 在当前位置上不满足条件的情形:
    (1) 若当前列不是最后一列,当前列设为下一列,返回到第2步;
    (2) 若当前列是最后一列了,回溯,即,若当前行已经是第一行了,算法退出,否则,清空当前行及以下各行的棋盘,然后,当前行设为上一行,当前列设为当前行的下一个待测位置,返回到第2步。

有了这个算法思路,接下来进行代码的编写。本来我考虑用二维数组存储每个棋子的位置坐标,这样也是大部分人第一时间想到的存储结构。不过,也可以用一个一维数组 location[i] 来存储位置信息,其中 i 是棋子所在的行数,location[i] 是棋子所在的列数。
再来看看怎么判断是位置否符合条件。一维数组每个位置代表一行,那么棋子在同一行的冲突情况肯定不会发生了;同一列方向上,检查数组中有没有其他地方与 location[i]的值一样;至于斜线方向,联系几何坐标系的内容,很容易知道两个位置的行数之差和列数之差的绝对值相等,则这两个棋子在同一斜线上。根据这个想法,写出方法 isAttackable()

1
2
3
4
5
6
7
private static boolean isAttackable(int row, int[] location) {
for (int i = 0; i < row; i++) {
if (location[row] == location[i] || Math.abs(location[row] - location[i]) == row - i)
return true;
}
return false;
}

接下来的回溯过程用递归来实现,虽然损失了一定的效率,但简单易懂。递归的过程在完整代码中有所体现。

程序代码

N皇后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class NQueens {
public List<List<String>> solveNQueens(int n) {
int[] location = new int[n];
List<List<String>> res = new ArrayList<>();
insertQueen(n, 0, location, res);
return res;
}

private static void insertQueen(int n, int row, int[] location, List<List<String>> res) {
if (row == n) { //当前行到达最后一行,构造结果
List<String> arrayList = new ArrayList<>();
for (int i = 0; i < n; i++) {
StringBuffer sb = new StringBuffer();
for (int j = 0; j < n; j++) {
if (j == location[i]) {
sb.append("Q");
} else {
sb.append(".");
}
}
arrayList.add(sb.toString());
}
res.add(arrayList);
return;
}
for (int i = 0; i < n; i++) { //遍历所有列
location[row] = i;
if (!isAttackable(row, location)) {
insertQueen(n, row + 1, location, res);
}
}
}

private static boolean isAttackable(int row, int[] location) {
for (int i = 0; i < row; i++) {
// 判断棋子是否符合放置条件
if (location[row] == location[i] || Math.abs(location[row] - location[i]) == row - i)
return true;
}
return false;
}
}

提交结果

N皇后II

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class NQueensII {
public int count = 0; //记录结果

public int totalNQueens(int n) {
if (n <= 0) {
return 0;
}
check(0, new int[n]);
return count;
}

private void check(int row, int[] location) {
if (row == location.length) { //已找到一种解法
count++;
return;
}
for (int i = 0; i < location.length; i++) {
location[row] = i;
if (!isAttackable(row, location)) {
check(row + 1, location);
}
}
}

private static boolean isAttackable(int row, int[] location) {
for (int i = 0; i < row; i++) {
// 判断棋子放置的合法性
if (location[row] == location[i] || Math.abs(location[row] - location[i]) == row - i)
return true;
}
return false;
}
}

提交结果