leetcode刷題之——N皇后

題目:

皇后問題研究的是如何將 n 個皇后放置在 n×n 的棋盤上,並且使皇后彼此之間不能相互攻擊。

上圖爲 8 皇后問題的一種解法。

給定一個整數 n,返回所有不同的 皇后問題的解決方案。

每一種解法包含一個明確的 n 皇后問題的棋子放置方案,該方案中 'Q' 和 '.' 分別代表了皇后和空位。

示例:

輸入: 4
輸出: [
 [".Q..",  // 解法 1
  "...Q",
  "Q...",
  "..Q."],

 ["..Q.",  // 解法 2
  "Q...",
  "...Q",
  ".Q.."]
]
解釋: 4 皇后問題存在兩個不同的解法。

思路:在看到這類題目的時候,首先想到的就是:圖搜索。這裏我使用的就是圖搜索,使用回溯算法進行解答。而在圖搜索中選擇深度優先搜索的難點主要在於以下幾點:

 1、結束時,記錄返回結果

 2、抉擇之後對應狀態的改變

 3、還原現場

其中對於困難點1,我們可以通過建立全局變量List<List<String>> 對象來進行填充,而dfs()的返回值可以置爲void。

對於困難點2是有一點麻煩的。我們可以通過對列進行搜索,每次dfs的時候將列+1(idx + 1),但是對於行,和斜線,我們需要專門考慮,爲此我們可以再設立幾個全局的數組來記錄對應的行和斜線的狀態。

困難點3,這個點是容易忽視掉的,類似於深搜和遞歸等,在改調用的方法結束的時候記得要還原現場,將狀態還原,避免影響之後的操作·。

以下是僞代碼:

List<List<String>> ans = new ArrayList<List<Stirng>>();

int[] path = new int[n];

void dfs(int idx,int n)// idx當前遍歷的列,n表示棋盤的邊長
    if(idx >= n) // 邊界條件,當搜索到邊界的時候,也就是改輸出結果的時候
        // 記錄結果 並結束
        return ;
    for(i...n)//對於每一列,皇后所在的行都是未知的、
        if(狀態是否符合搜索條件) //剪枝判斷
            path[idx] = i; //表示該列所對應的行
            //記錄狀態
            dfs(idx+1,n);
            //還原狀態

public List<List<Stirng>> solveNQueens(int n){
    dfs(0,n);
    return ans
}

觀上面的僞代碼,可以知道主要難點在於剪枝判斷和記錄結果。其實記錄結果還是很簡單的,只需要遍歷整個棋盤,記錄該列所對應的行並標記爲Q就行。所以,對於整個算法的難點就落在了剪枝操作和記錄狀態上面了。因爲剪枝操作其實就是判斷對應的狀態,所以所有的難點也就是記錄狀態。

記錄狀態的難點:由於皇后可以走八個方向,四條直線。因此可以歸結爲一下四種情況

1、行

2、列

3、正斜線

4、反斜線

由於我們的搜索是以列展開的,因此列這條直線可以直接省去。而行的考慮還是比較簡單的,我們只需要在記錄狀態的時候標記改行已經被佔用就可以了。可以定義一個全局變量rows[i] = true,來表示改行是否被佔用,記得之後要還原現場。

對於正斜線和反斜線,我們就要仔細考慮了。我們可以參照行進行操作。由於這個狀態的記錄主要是表示那些未知被皇后佔用了。所以我們可以將斜線也類似於行一樣標記出來。對於一個n*n的棋盤來說正斜線和反斜線的數量都是 2n-1。而我們想要將被佔用的斜線表示出來,可以才用數學中的思維來進行解決,構件座標軸如下:

雖然醜了點,不過可以看= =。我們將正斜線作爲x+y=k固定值,那麼對於不同的k值,即可以表示爲不同的正斜線。那麼對於正斜線的全局變量slash數組的下標可以記爲slash[x+y],即slash[idx + i] = true;而反斜線的方程是-x+y = k。同理可以記爲backlash[idx-i] = true;不過idx - i可能是負數,所以我們要將其設置爲正數,不然下標就出錯了,因此可以定爲backlash[idx-i+n-i] = true;記錄完狀態之後,再調用dfs之後需要還原現場。

由以上的思考方式最後可以得到最後的代碼爲以下:

    List<List<String>> ans = new ArrayList<List<String>>();

    int[] row;

    boolean[] line;

    boolean[] slash;

    boolean[] backlash;

    /**
     * 對列進行深度優先搜索
     * @param idx
     * @param n
     */
    void dfs(int idx,int n){
        //結束條件   記錄結果
        if(idx >= n){
            List<String> tmp = new ArrayList<String>();
            for(int i = 0 ; i < n ; i++){
                String str = "";
                for(int j = 0 ; j < n ; j++){
                    if(row[i] == j){
                        str +="Q";
                    }else{
                        str +=".";
                    }
                }
                tmp.add(str);
            }
            ans.add(tmp);
            return ;
        }

        for(int i = 0 ; i < n ; i++){
            if(!line[i] && !slash[idx + i] && !backlash[idx-i+n-1]){
                row[idx] = i;
                line[i] = true;
                slash[i+idx] = true;
                backlash[idx-i+n-1] = true;
                dfs(idx + 1 , n);
                line[i] = false;  // 還原現場
                slash[i+idx] = false;
                backlash[idx-i+n-1] = false;
            }
        }
    }

    public List<List<String>> solveNQueens(int n) {
        row = new int[n];
        line = new boolean[n];
        slash = new boolean[2*n];
        backlash = new boolean[2*n];
        dfs(0,n);
        return ans;
    }

  這道題目還是挺有意思的,主要是很有做的意義。在剛剛拿到題目的時候感覺考慮的點有很多,但是在一步一步解析之後,再看這道題目,就會發現並沒有多難。而且這道題目很具有代表性,可以說是圖的深搜的代表了。筆者目前也是慢慢在學習圖的相關算法,解題思路比較簡陋,代碼還有很多可以優化的點。歡迎大家在閱讀完之後,如果有什麼想法和意見的話,能夠及時告訴我,我好及時修正。

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