力扣日记:【回溯算法篇】51. N 皇后
日期:2023.3.6
参考:代码随想录、力扣
51. N 皇后
题目描述
难度:困难
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
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
题解
cpp ver
class Solution {
public:
vector<vector<string>> result;
vector<vector<string>> solveNQueens(int n) {
// 初始化 棋盘
// 注意对于一个二维矩阵,board[row][col] 即索引是先row再col
vector<string> chessboard(n, string(n, '.'));
backtracking(chessboard, n, 0);
return result;
}
bool isValid(vector<string> chessboard, int n, int x, int y) { // y -> row, x -> col,索引先y再x
// 同一行是否有Q(由于是递归进入下一行,所以不可能在同一行有Q,不需判断)
// 同一列是否有Q(由于是从上往下遍历,所以下方未遍历过的行不可能有Q,只需判断当前位置以上的部分)
for (int i = 0; i < y; i++) {
if (chessboard[i][x] == 'Q') return false;
}
// \ 是否有Q(同理,只需判断左上,注意m,k从当前位置的左上一个位置开始)
for (int m = x - 1, k = y - 1; m >= 0 && k >= 0; m--, k--) { // m 为 横坐标,k 为纵坐标 查询:[0,x-1) [0,y-1)
if (chessboard[k][m] == 'Q') return false;
}
// / 是否有Q(同理,只需判断右上,注意m,k从当前位置的右上一个位置开始)
for (int m = x + 1, k = y - 1; m < n && k >= 0; m++, k--) { // [x+1, n) [0,y-1)
if (chessboard[k][m] == 'Q') return false;
}
return true;
}
// 关键:将N皇后问题转换为树形结构:横向搜索为for循环,纵向遍历(进入下一行)为递归
// 参数:棋盘矩阵,皇后数,当前遍历到的行数
void backtracking(vector<string>& chessboard, int n, int row) {
// 终止条件:row从0开始,当为n时,说明已经遍历完所有行
if (row == n) {
result.push_back(chessboard);
return;
}
// for 循环
for (int i = 0; i < n; i++) { // 每一行都从0开始遍历
if (isValid(chessboard, n, i, row)) {
// 如果当前位置为有效位置
// 设置此位置为皇后位置
chessboard[row][i] = 'Q';
// 递归(进入下一行)
backtracking(chessboard, n, row + 1); // row 作为参数,自动回溯
// 回溯
chessboard[row][i] = '.';
}
}
}
};
复杂度
时间复杂度: O(n!)
空间复杂度: O(n)
思路总结
- 第一次接触到N皇后,即使知道用回溯法,还是不知道如何下手。再次感谢代码随想录提供思路。
- 关键思路:
-
将N皇后问题转换为树形结构:依次遍历棋盘的各个位置,横向搜索通过for循环,纵向遍历(进入下一行)通过递归
棋盘的宽度就是for循环的长度,递归的深度就是棋盘的高度
-
只有当前位置有效,才能放入棋盘(处理节点)并进行递归回溯
-
- 难点(易错):判断当前位置是否有效
- 首先要明确,对于二维数组,row为第一个索引,col为第二个索引
- 判断当前位置是否有效,即当前位置的同一行、同一列、左45°、右45°(同斜线)均不能出现皇后’Q’
- 剪枝:由于遍历是从左到右、从上往下(一行接一行)遍历,所以在判断时:
- 对于同一行是否有Q:由于是递归进入下一行,所以不可能在同一行有Q,不需判断
- 对于同一列是否有Q:由于是从上往下遍历,所以下方未遍历过的行不可能有Q,只需判断当前位置以上的部分
- 对于 \ 方向是否有Q:同理,只需判断左上,注意m(横坐标),k(纵坐标)从当前位置的左上一个位置开始,即m:x-1 → 0,k:y-1 → 0;
- 对于 / 方向是否有Q:同理,只需判断右上,注意m,k从当前位置的右上一个位置开始,即 m:x+1 → n - 1,k:y -1 → 0。
- 三部曲:
- 参数: 棋盘矩阵,皇后数,当前遍历到的行数
- 棋盘的初始化:
vector<string> chessboard(n, string(n, '.'));
- 棋盘的初始化:
- 终止条件:row从0开始,当为n时,说明已经遍历完所有行,则终止
- for 循环
- 对棋盘的第row行,从棋盘左遍历到右
- 如果当前位置有效,则进行:
- 处理节点即放下皇后(
chessboard[row][i] = 'Q';
) - 递归(进入下一行)
- 回溯(
chessboard[row][i] = '.';
)
- 处理节点即放下皇后(
- 参数: 棋盘矩阵,皇后数,当前遍历到的行数
- 树状结构示意图(n = 3)