今天学习用回溯算法解决大名鼎鼎的N皇后问题。
51.N皇后
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
示例 1:
输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。
思路:
1.在这之前回溯算法所解决的有关组合、分割、子集、排列问题中所给的都是一维的数组,而N皇后则是给了一个矩阵,思考后我们进一步将问题拆分转化为两个问题:(1)递归和遍历的规则?(2)如何判断当前位置能够放置皇后?
2.如果能够将二维矩阵转化为一个树形结构,会发现如何遍历的问题就有了答案:我们可以把树层遍历当做是遍历每一行,而纵向的树枝递归则是在当前行找到了一个能够放置皇后的点并将其放下后进入下一行。
3.而如何判断当前位置是否有效,实际上我们只需要遍历当前位置的列,45度角的方向,135度角的方向,以及当前位置的行看这些位置有没有别的皇后即可。
class Solution {
public:
vector<vector<string>> result;
//判断该位置皇后是否有效
bool isValid(int row, int col, vector<string>& chessboard, int n){
//遍历列
for(int i = 0; i < row; i++){
if(chessboard[i][col] == 'Q') return false;
}
//遍历左上斜线
for(int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--){
if(chessboard[i][j] == 'Q') return false;
}
//遍历右上斜线
for(int i = row - 1, j = col + 1; i >= 0 && j <= n; i--, j++){
if(chessboard[i][j] == 'Q') return false;
}
return true;
}
void backtracking(int n, int row, vector<string>& chessboard){
//当行数等于n,说明到达最底层且放置好了最后一个皇后,找到一种有效解
if(row == n){
result.push_back(chessboard);
return;
}
//单行遍历
for(int i = 0; i < n; i++){
if(isValid(row, i, chessboard, n)){
chessboard[row][i] = 'Q';
backtracking(n, row + 1, chessboard);
chessboard[row][i] = '.';
}
}
}
vector<vector<string>> solveNQueens(int n) {
result.clear();
vector<string> chessboard(n, std::string(n, '.'));
backtracking(n, 0, chessboard);
return result;
}
};
启发:
1.经过优化后发现其实在判断当前位置能否放置皇后时没有必要遍历当前行以及当前行之下的45度角和135度角的方向(即左下和右下),因为我们的递归遍历是从第一行开始逐行向下递归的,一旦遇到不符合条件的也会回溯,因此当前位置的下方一定没有皇后,而每一行的for循环遍历实际上已经帮助我们进行了当前行的判断,所以我们只需要判断当前列,以及左上和右上方向是否有其他皇后即可。
题外话:
之前在做一家公司的面试题时遇到过弱化版的N皇后,当时的题意是在一个n*n的棋盘上帮我们放置好了一些皇后,此时让我们再放置一个皇后,问有多少个地方能够放置。
当时自己的思路时用一个二维的布尔矩阵来判断当前位置是否能够放置皇后,然后遍历整个棋盘矩阵,遇见一个皇后就将当前位置以及当前位置的行、列、45度角、135度角的所有点全部设置为false,最后再遍历一遍布尔矩阵记录下仍为true的点的个数即为最终的结果。