1题目
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
n 皇后问题 研究的是如何将 n
个皇后放置在 n×n
的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n
,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q'
和 '.'
分别代表了皇后和空位。
示例 1:
输入:n = 4 输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]] 解释:如上图所示,4 皇后问题存在两个不同的解法。
示例 2:
输入:n = 1 输出:[["Q"]]
提示:
1 <= n <= 9
2链接
题目链接:51. N 皇后 - 力扣(LeetCode)
视频链接:这就是传说中的N皇后? 回溯算法安排!| LeetCode:51.N皇后_哔哩哔哩_bilibili
3解题思路
这个题属实有点烦
首先来看一下皇后们的约束条件:
- 不能同行
- 不能同列
- 不能同斜线
搜索皇后的位置,可以抽象为一棵树。
下面我用一个 3 * 3 的棋盘,将搜索过程抽象为一棵树,如图:
(*狗屎,ctrl+z给我全部撤销完了,他奶奶的)
从图中,可以看出,二维矩阵中矩阵的高就是这棵树的高度,矩阵的宽就是树形结构中每一个节点的宽度。
那么我们用皇后们的约束条件,来回溯搜索这棵树,只要搜索到了树的叶子节点,说明就找到了皇后们的合理位置了
回溯三部曲:
1、确定函数参数及返回值
定义全局变量二维数组result来记录最终结果。
参数n是棋盘的大小,然后用row来记录当前遍历到棋盘的第几层了。
vector<vector<string>> result;
void backtracking(int n, int row, vector<string>& chessboard) {
2、确定递归终止条件
由上图可以看出,当递归到棋盘最底层(也就是叶子节点)的时候,就可以收集结果并返回了。
if (row == n) {
result.push_back(chessboard);
return;
}
3、确定单层搜索逻辑
递归深度就是row控制棋盘的行,每一层里for循环的col控制棋盘的列,一行一列,确定了放置皇后的位置。
每次都是要从新的一行的起始位置开始搜,所以都是从0开始。
for (int col = 0; col < n; col++) {
if (isValid(row, col, chessboard, n)) { // 验证合法就可以放
chessboard[row][col] = 'Q'; // 放置皇后
backtracking(n, row + 1, chessboard);
chessboard[row][col] = '.'; // 回溯,撤销皇后
}
}
前面的回溯搞完了,剩下的就是判定合法性了
去重标准:
- 不能同行
- 不能同列
- 不能同斜线 (45度和135度角)
这里要注意了,实际上不用显式的去重行。因为回溯算法的特性,每一次递归都会在下一行中放置皇后,因此不可能在同一行中出现两个皇后。也就是说,在isValid的调用过程中,只有一行中的一个位置会被检查,因此不需要显式地检查该行是否已经放置了皇后。
不能同列:递归到第row行,且for循环遍历到第col列时-->检查第0-row行第col列是否已经出现了Q,如果出现则已不能再放置,设为不合法。
for (int i = 0; i < row; i++) { // 这是一个剪枝
if (chessboard[i][col] == 'Q') {
return false;
}
}
不能同45°斜线:递归到第row行col列时,以此格左上角的一格为起点,一直往左上角去搜索(行--,列--),看是否出现了皇后
for (int i = row - 1, j = col - 1; i >=0 && j >= 0; i--, j--) {
if (chessboard[i][j] == 'Q') {
return false;
}
}
不能同135°斜线:递归到第row行col列时,以此格右上角的一格为起点,一直往右上角去搜索(行--,列++),看是否出现了皇后
for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
if (chessboard[i][j] == 'Q') {
return false;
}
}
应该没得问题了,理一下整体思路:
row和col分别控制递归深度和广度-->递归到一个格子就检查合法性-->合法则在这个格子上放下皇后-->回溯-->递归到叶子节点收集结果。
4代码
class Solution {
private:
vector<vector<string>> result;
// n 为输入的棋盘大小
// row 是当前递归到棋盘的第几行了
void backtracking(int n, int row, vector<string>& chessboard) {
if (row == n) {
result.push_back(chessboard);
return;
}
for (int col = 0; col < n; col++) {
if (isValid(row, col, chessboard, n)) { // 验证合法就可以放
chessboard[row][col] = 'Q'; // 放置皇后
backtracking(n, row + 1, chessboard);
chessboard[row][col] = '.'; // 回溯,撤销皇后
}
}
}
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;
}
}
// 检查 45度角是否有皇后
for (int i = row - 1, j = col - 1; i >=0 && j >= 0; i--, j--) {
if (chessboard[i][j] == 'Q') {
return false;
}
}
// 检查 135度角是否有皇后
for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
if (chessboard[i][j] == 'Q') {
return false;
}
}
return true;
}
public:
vector<vector<string>> solveNQueens(int n) {
result.clear();
std::vector<std::string> chessboard(n, std::string(n, '.'));
backtracking(n, 0, chessboard);
return result;
}
};