文章同步发布在我的个人博客(zhuoerhuobi.cn)
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
(上图为 8 皇后问题的一种解法。)
给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。
每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
示例:
输入: 4
输出: [
[".Q…", // 解法 1
“…Q”,
“Q…”,
“…Q.”],["…Q.", // 解法 2
“Q…”,
“…Q”,
“.Q…”]
]
解释: 4 皇后问题存在两个不同的解法。
提示:
- 皇后,是国际象棋中的棋子,意味着国王的妻子。皇后只做一件事,那就是“吃子”。当她遇见可以吃的棋子时,就迅速冲上去吃掉棋子。当然,她横、竖、斜都可走一到七步,可进可退。(引用自 百度百科 - 皇后 )
思路
N皇后经典递归算法题,写了无数遍。新手可以从这道题里学到很多回溯、标记、DFS等基础却又很有用的概念。
研究算法的本质是使用更短的时间和更小的空间解决问题。N皇后最简单的思路就是放上N个棋子判断,但是复杂度是O(n^n),明显不可取。如何降低复杂度?
可以很容易想到一种方法,我随意放置一个棋子,放下去以后就会有部分格子不能放棋子了(会被吃),那我接下来的选择是不是就变少了,越往后选择余地就越小,也就大大降低了时间复杂度。如何知道某个格子能不能放棋子?所以我们就会接触到染色(标记)的概念,也就是给不能放的格子进行标记,一个听起来平平无奇却又几乎贯彻在所有算法中的小技巧。
接下来还能怎么优化?我们又想到,每一行必定只能放一个皇后,所以我们只要关注接下来没放棋子的行,并且只需要关注皇后竖向和斜向能吃到哪里。在某行如果没有可以放棋子的格子怎么办?所以我们需要回溯。回溯就需要返回之前的状态,如何返回?所以我们需要在DFS前保存状态,DFS后还原状态,也就类似于锁。
Java实现
class Solution {
static int N;
static List<List<String>> res;
static String[] chessBoard;
static int[][] color;
public List<List<String>> solveNQueens(int n) {
res = new ArrayList<>();
chessBoard = new String[n];
color = new int[n+1][n+1];
N = n;
dfs(1);
return res;
}
public static void dfs(int row) {
//递归都要有边界条件。
if (row > N) {
res.add(new ArrayList<>(Arrays.asList(chessBoard)));
return;
}
for (int i = 1; i <= N; i++) {
if (color[row][i] == 0) {
dyeing(row, i);
dfs(row+1);
fade(row, i); //dfs完要还原状态,这里的操作是不是很像“锁”?
}
}
}
public static void dyeing(int row, int column) {
StringBuilder temp = new StringBuilder();
for (int i = 0; i < N; i++) {
if (i == column-1) {
temp.append('Q');
}
else {
temp.append('.');
}
}
chessBoard[row-1] = temp.toString();
for (int i = row+1; i <= N; i++) {
color[i][column]++;
}
for (int i = 1; Math.max(row, column)+i <= N; i++) {
color[row+i][column+i]++;
}
for (int i = 1; row+i <= N && column-i > 0; i++) {
color[row+i][column-i]++;
}
}
public static void fade(int row, int column) {
chessBoard[row-1] = "";
for (int i = row+1; i <= N; i++) {
color[i][column]--;
}
for (int i = 1; Math.max(row, column)+i <= N; i++) {
color[row+i][column+i]--;
}
for (int i = 1; row+i <= N && column-i > 0; i++) {
color[row+i][column-i]--;
}
}
}
从一开始的摸不着头脑,到如今的驾轻就熟,每次做N皇后我都有不同的感受。其实题刷多了,会发现都是那些套路(不包括少部分难题)。所以刷题不在于多,一定要在刷的过程中注意总结,递归、贪心、DP、DFS、BFS、回溯这些概念要烂熟于心。做题时要把握住题目的本质,理解它到底是在考察什么知识。
题刷百遍,其意自现。