51. N-Queens
The n-queens puzzle is the problem of placing n queens on an n×n chessboard such that no two queens attack each other.
Given an integer n, return all distinct solutions to the n-queens puzzle.
Each solution contains a distinct board configuration of the n-queens' placement, where 'Q'
and '.'
both indicate a queen and an empty space respectively.
For example,
There exist two distinct solutions to the 4-queens puzzle:
[ [".Q..", // Solution 1 "...Q", "Q...", "..Q."], ["..Q.", // Solution 2 "Q...", "...Q", ".Q.."] ]
題目大意:
給定一個n*n的棋盤,在棋盤裏放入n個Q,要求每一行,每一列都只有一個Q,而且每個Q的對角線上只能有一個Q。
思路:
這是一個典型的回溯問題。一條路走到黑,走到黑了退回來一步,然後再走,直到走通一條路。退回來繼續尋找其他出路。
解決這個問題需要解決幾個關鍵點:
1.關於保存一個可行路線的數據結構的選擇
保存一個可行路線的時候,選擇數據結構時選擇一個2維數組還是什麼這裏需要動腦筋了,最後衡量,選擇了一維數組。
比如說4-Queue時一個合法路線爲
[".Q..", // Solution 1 "...Q", "Q...", "..Q."],
那麼它對應的1位數組爲{1,3,0,2}
解釋:
1是數組第0位,對應二維數組第0行,對應的值是1,表示在第0行時,第1列可以放Q。
3是數組第1位,對應二維數組第1行,對應的值是3,表示在第1行時,第3列可以放Q。
0是數組第2位,對應二維數組第3行,對應的值是0,表示在第2行時,第0列可以放Q。
2是數組第3位,對應二維數組第3行,對應的值是2,表示在第3行時,第2列可以放Q。
數組的下標爲行,數組的元素值爲列。通過行列來確定Q的位置。
2.關於如何判斷要加入的元素是否爲合法
要插入一個合法元素到合法位置上,需要判斷是否合法,在這裏使用了函數
bool isValid(int curIndex, int val ,int n, vector<int> &a)
curIndex表示當前要插入的元素在合法數組中的下標,也就是行。val是要插入的列值。n是有多少個Queue,a是保存臨時合法路線的一個數組。
判斷a[i] == val 相等說明該列重複。
對角線的斜率爲1或-1,所以如果 |val - a[i]| / |cur - i| == 1,那麼說明在同一對角線上。
3.將合法的路徑一個一個的放入結果臨時數組中。從結果臨時數組轉換成結果數組。
代碼如下:
class Solution { public: //判斷當前要插入的值val在這個位置curIndex是否合法 bool isValid(int curIndex, int val ,int n, vector<int> &a) { if (curIndex < n ) { for (int i = curIndex - 1; i >= 0; --i) { if (a[i] == val)//判斷列上是否有重複的 return false; } for (int i = curIndex - 1; i >= 0; --i) { if (abs(val - a[i]) == (curIndex - i))//判斷斜線上是否有重複的 { return false; } } return true; } return false; } void PutQueens(int val , vector<int> &a)//將合法的值放入當前臨時結果數組 { a.push_back(val); } //start開始的行數,也就是第start+1個Queue的放置 void solveNQueensToIntVector(int start,int n, vector<int> &cur, vector<vector<int>> &result)//先轉換成int來處理 { if (cur.size() == n) { result.push_back(cur); return; } if (start == n) return; //典型的回溯套路 for (int i = 0; i < n; i++) { if (!isValid(cur.size(),i, n, cur)) continue; vector<int> temp(cur);//保存變化前的vector PutQueens(i, cur); solveNQueensToIntVector(start + 1, n, cur, result); cur.swap(temp); } } vector<vector<string>> solveNQueens(int n) { vector<int> cur; vector<vector<int>> tempResult; vector<vector<string>> result; solveNQueensToIntVector(0,n,cur,tempResult); //vector<vector<int>> tempResult 轉換成 目標結果result for (int i = 0; i < tempResult.size(); i++) { vector<string> floorVector; string floor; for (int j = 0; j < tempResult[i].size(); j++) { for (int k = 0; k < tempResult[i][j]; k++) { floor += "."; } floor += "Q"; for (int k = tempResult[i][j] + 1; k < n ; k++) { floor += "."; } floorVector.push_back(floor); floor.clear(); } result.push_back(floorVector); floorVector.clear(); } return result; } };
本題總結:
回溯問題在N-Queue這道題中體現的十分明顯。
回溯法解題思路(1)針對所給問題,定義問題的解空間;
(2)確定易於搜索的解空間結構;
(3)以深度優先方式搜索解空間,並在搜索過程中用剪枝函數避免無效搜索。
回溯就是讓計算機自動的去搜索,碰到符合的情況就結束或者保存起來,在一條路徑上走到盡頭也不能找出解,就回到原來的岔路口,選擇一條以前沒有走過的路繼續探測,直到找到解或者走完所有路徑爲止。就這一點,回溯和所謂的DFS(深度優先搜索)是一樣的。那現在的關鍵是,怎麼實現搜索呢?回溯既然一般使用遞歸來實現,那個這個遞歸調用該如何來寫呢?我的理解就是,先判斷這一次試探是否有效,如果有效則加入這個元素,然後進行下一次遞歸,遞歸後恢復加入這個合法元素之前的狀態,進行下一次循環;如果無效則直接進行下一次循環。
2016-08-15 15:53:37