文章同步發佈在我的個人博客(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、回溯這些概念要爛熟於心。做題時要把握住題目的本質,理解它到底是在考察什麼知識。
題刷百遍,其意自現。