###### tags: `LeetCode` `Recursion` `Hard` `DFS` `String` # LeetCode #51 [N-Queens](https://leetcode.com/problems/n-queens/) ### (Hard) n 皇后問題 研究的是如何將 n 個皇后放置在 n×n 的棋盤上,並且使皇后彼此之間不能相互攻擊。 給你一個整數 n ,返回所有不同的 n 皇后問題 的解決方案。 每一種解法包含一個不同的 n 皇后問題 的棋子放置方案,該方案中 'Q' 和 '.' 分別代表了皇后和空位。 ![](https://i.imgur.com/mhOBTXx.png) --- 跟 [#37](https://hackmd.io/CDTAgNMPTkqUPXTv6yHXuQ) 數獨題類似, 重點同樣有兩個: 1. 如何遍歷整個棋盤 2. 如何判斷皇后的落點是合法的 有一個重點, DFS遞迴只能處理一維資料, 在#37中需要嘗試的資料有兩類 * 數字填入位置(而又將整個二維陣列透過進位轉成一維-行數等於n時呼叫下一列並將行數歸零) * 每個位置又可以填入1-9 因此在 [#37](https://hackmd.io/CDTAgNMPTkqUPXTv6yHXuQ) 中透過一個for迴圈加上遞迴呼叫解決兩類資料。 在這題中, 將每一行獨立處理會輕鬆許多, 因此位置資料為二維矩陣, 而填入資料(皇后)不需要考慮差異(不像數獨題每個都要考慮1-9), 因此這題還是二維資料處理, 使用一個for迴圈加上遞迴呼叫即可。 前面提到用for迴圈加上遞迴, 對應到資料型態, 一個負責行(遞迴), 一個負責列(for迴圈), 因此, 程式碼為for迴圈從第0列開始, 每次遞迴檢查該行的皇后落點是否合法, 而終止條件為列數等於n(因為每一列都一定有一個皇后, 所以可以確定此時皇后數量已達最大值, 不需要額外檢查), 此時將棋盤存入答案數組中然後返回。 對應遍歷方式(由上層列往下層列, 每一列尋找該列能落子皇后的位置(行)), 在皇后落子合法性的檢查可以省去一些不必要的檢查, 如:同列(橫向)檢查(因為每一列都只會放一個皇后, 因此只要做前面幾列的同行(直向)檢查即可), 以及左下及右下檢查(因為是從上面列往下做, 因此下面列都還是空的, 只要檢查左上右上即可)。 最後, 在檢查左上右上時的for迴圈執行條件, (i>=0&&j<n)以及(i>=0&&j>=0), 要注意是用&&而非||, 因為for迴圈的第二個參數是執行條件而非終止條件, 使用||的話必須兩者皆不滿足才會停止, 會造成溢位。 --- ``` class Solution { public: vector<vector<string>> solveNQueens(int n) { vector<string> tmp(n, string(n,'.')); vector<vector<string>> ans; dfs(ans, tmp, 0, n); return ans; } void dfs(vector<vector<string>>& ans, vector<string>& tmp, int row, int n){ if(row==n){ ans.push_back(tmp); return; } for(int column=0;column<n;column++) if(isvalid(tmp, row, column, n)){ tmp[row][column]='Q'; dfs(ans, tmp, row+1, n); tmp[row][column]='.'; } } bool isvalid(vector<string>& board, int row, int col, int& n){ for(int i=0;i<row;i++){//直的 if(board[i][col]=='Q')return false; } for(int i=row-1, j=col+1;i>=0&&j<n;i--,j++){//右上 if(board[i][j]=='Q')return false; } for(int i=row-1, j=col-1;i>=0&&j>=0;i--,j--){//左上 if(board[i][j]=='Q')return false; } return true; } }; ```