LeetCode—51.N皇后

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

題刷百遍,其意自現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章