1. 程式人生 > >【LeetCode/LintCode】丨Google面試題:N皇后問題

【LeetCode/LintCode】丨Google面試題:N皇后問題

n皇后問題是將n個皇后放置在n*n的棋盤上,皇后彼此之間不能相互攻擊(任意兩個皇后不能位於同一行,同一列,同一斜線)。

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

每個解決方案包含一個明確的n皇后放置佈局,其中“Q”和“.”分別表示一個女王和一個空位置。

線上評測地址:

LintCode 領釦​

樣例1:

輸入:1
輸出:
   [["Q"]]

樣例2:

輸入:4
輸出:
[
  // Solution 1
  [".Q..",
   "...Q",
   "Q...",
   "..Q."
  ],
  // Solution 2
  ["..Q.",
   "Q...",
   "...Q",
   ".Q.."
  ]
]

演算法:dfs(回溯法)

題目分析

這個問題要求把n個皇后放在一個nXn的棋盤上,使得任何兩個皇后都不能相互攻擊,即它們不能同行,不能同列,也不能位於同一條對角線上。對於n=1,問題的解很簡單,而且很容易看出對於n=2和n=3來說,這個問題是無解的。所以我們考慮4皇后問題,並用回溯法對它求解。

演算法思路

  • 因為每個皇后都必須分別佔據一行,我們需要做的不過是棋盤上的每個皇后分配一列。
  • 下面我們用4皇后的求解過程來講解演算法思路:
從空棋盤開始,然後把皇后1 放到它所在行的第-一個可能位置上,也就是第一-行第一列。對於皇后2,在經過第-列和第二列的失敗嘗試之後,我們把它放在第一個可能的位置,就是格子(2, 3),位於第二行第三列的格子。這被證明是一個死衚衕,因為皇后3將沒有位置可放。所以,該演算法進行回溯,把皇后2放在下一個可能位置(2,4)上。這樣皇后3就可以放在(3, 2),這被證明是另一個死衚衕。該演算法然後就回溯到底,把皇后1移到(1,2)。 接著皇后2到(2,4), 皇后3到(3,1), 而皇后4到(4, 3), 這就是該問題的一個解。
  • 整個過程實際上就是一個狀態樹的遍歷過程
  • 下圖為狀態樹

演算法:dfs(回溯法)

題目分析

這個問題要求把n個皇后放在一個nXn的棋盤上,使得任何兩個皇后都不能相互攻擊,即它們不能同行,不能同列,也不能位於同一條對角線上。對於n=1,問題的解很簡單,而且很容易看出對於n=2和n=3來說,這個問題是無解的。所以我們考慮4皇后問題,並用回溯法對它求解。

演算法思路

  • 因為每個皇后都必須分別佔據一行,我們需要做的不過是棋盤上的每個皇后分配一列。
  • 下面我們用4皇后的求解過程來講解演算法思路:
從空棋盤開始,然後把皇后1 放到它所在行的第-一個可能位置上,也就是第一-行第一列。對於皇后2,在經過第-列和第二列的失敗嘗試之後,我們把它放在第一個可能的位置,就是格子(2, 3),位於第二行第三列的格子。這被證明是一個死衚衕,因為皇后3將沒有位置可放。所以,該演算法進行回溯,把皇后2放在下一個可能位置(2,4)上。這樣皇后3就可以放在(3, 2),這被證明是另一個死衚衕。該演算法然後就回溯到底,把皇后1移到(1,2)。 接著皇后2到(2,4), 皇后3到(3,1), 而皇后4到(4, 3), 這就是該問題的一個解。
  • 整個過程實際上就是一個狀態樹的遍歷過程
  • 下圖為狀態樹

程式碼思路

  • 按行擺放,在確定一個皇后應該擺的列時,需要檢查當前列是否合法,如果合法,則將皇后放置在當前位置,並進行遞迴,回溯。每行都擺滿皇后時,則產生了一種解法,將所有解法收集並返回。
  • 合法性判斷方法:當前將要擺放皇后的位置和其他已擺放皇后的位置不能在同一列,且不能在同一條斜線上。這裡判斷是否在同一條斜線上可以通過兩個皇后的位置橫座標之差和縱座標之差的絕對值是否相等來判斷。

複雜度分析

  • 空間複雜度:O(N!)
  • 時間複雜度:O(N!)
  • 放置第一個皇后有 N 種可能,放置兩個皇后不超過N(N-2)種可能,放置三個皇后不超過N(N - 2)(N - 4)種可能 ,以此類推。
class Solution {
    /**
     * Get all distinct N-Queen solutions
     * @param n: The number of queens
     * @return: All distinct solutions
     * For example, A string '...Q' shows a queen on forth position
     */
    List<List<String>> solveNQueens(int n) {
        // result用於儲存答案
        List<List<String>> results = new ArrayList<>();
        if (n <= 0) {
            return results;
        }
        
        search(results, new ArrayList<Integer>(), n);
        return results;
    }
    
    // search函式為搜尋函式,n表示已經放置了n個皇后,cols 表示每個皇后所在的列
    private void search(List<List<String>> results, List<Integer> cols, int n) {
        // 若已經放置了n個皇后表示出現了一種解法,繪製後加入答案result
        if (cols.size() == n) {
            results.add(Draw(cols));
            return;
        }
        // 列舉當前皇后放置的列,若不合法則跳過
        for (int colIndex = 0; colIndex < n; colIndex++) {
            if (!isValid(cols, colIndex)) {
                continue;
            }
            // 若合法則遞迴列舉下一行的皇后
            cols.add(colIndex);
            search(results, cols, n);
            cols.remove(cols.size() - 1);
        }
    }
    
    // isValid函式為合法性判斷函式
    private boolean isValid(List<Integer> cols, int col) {
        int row = cols.size();
        for (int rowIndex = 0; rowIndex < cols.size(); rowIndex++) {
            //若有其他皇后在同一列或同一斜線上則不合法
            if (cols.get(rowIndex) == col) {
                return false;
            }
            if (row + col == rowIndex + cols.get(rowIndex)) {
                return false;
            }
            if (row - col == rowIndex - cols.get(rowIndex)) {
                return false;
            }
        }
        return true;
    }
    // Draw函式為將 cols 陣列轉換為答案的繪製函式
    private List<String> Draw(List<Integer> cols) {
        List<String> result = new ArrayList<>();
        for (int i = 0; i < cols.size(); i++) {
            StringBuilder sb = new StringBuilder();
            for (int j = 0; j < cols.size(); j++) {
                sb.append(j == cols.get(i) ? 'Q' : '.');
            }
            result.add(sb.toString());
        }
        return result;
    }
}

更多題解參考:

九章演算法 - 幫助更多中國人找到好工作,矽谷頂尖IT企業工程師實時線上授課為你傳授面試技