持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第30天,点击查看活动详情
题目
题目链接: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
解题思路
N 皇后算是一道很经典的递归回溯的题了。
👉递归
这题只需要遍历所有情况即可,但需要对每行每列和每个对角进行标记。其实思路不是太难想到,就是具体实现的代码有点难实现。思路就是遍历这个二维数组,尝试每个空位放入 Q ,看看是否满足题意。递归的深度和宽度都就是这个二维数组的横纵数,深度就是上图的纵坐标,宽度就是上图的横坐标,即 n 。具体做法:
-
递归函数的参数
n,需要用皇后数作为返回条件和遍历范围,因为递归的深度和宽度都是nm,作为遍历的深度
-
递归函数的返回条件
当递归到
n时就可以返回,同时把满足的情况放入二维数组中 -
递归函数的功能
由于递归的深度是“皇后数组”的纵坐标(从上向下看),所以递归函数里面需要遍历数组的横坐标,判断当前位置是否满足条件(横纵和两条对角线都没有皇后),满足就将其放入皇后(
Q)做好标记,进入下一层(因为如果一层放入了一个皇后,那么这一层就不会再放了)。递归结束后要恢复原状。
这里有一个难点,就是标记横纵和两条对角线有无皇后。如果使用二维数组标记,那么每次放入一个皇后前都需要循环遍历横纵和两条对角线,放入后还需要重复遍历。对于横纵两条线可以用一个一维数组处理,因为如果当前位置放入皇后,那么就会进入下一层,所以横着的线可以不用标记,那么纵着的线可以用当前的纵坐标标记。对于对角线如何标记是一个难点(数学厉害的另说:stuck_out_tongue:),如下图
根据同一直线的截距相同,只要把皇后所在的那条线的截距作为下标进行标记即可。所以两条对角线可以分别用一个一维数组标记。这样这题就解决了
代码(C++)
递归
class Solution {
public:
vector<vector<string>> str;
vector<string> ss;
bool h[20], s[20], l[20];
void dfs(int n, int m) {
if (n == m) {
str.push_back(ss);
return ;
}
for (int i = 0; i < n; ++ i) {
if (!h[i] && !s[i - m + n] && !l[m + i]) { //这里需要加n,因为i可能小于m
ss[m][i] = 'Q';
h[i] = s[i - m + n] = l[m + i] = true;
dfs(n, m + 1);
h[i] = s[i - m + n] = l[m + i] = false;
ss[m][i] = '.';
}
}
}
vector<vector<string>> solveNQueens(int n) {
for (int i = 0; i < n; ++ i) {
string a;
for (int j = 0; j < n; ++ j) {
a += '.';
}
ss.push_back(a);
}
dfs(n, 0);
return str;
}
};
总结
这题的思路不难想到,只是一些细节和几个难点加在一起就让整体的代码难以实现。这题我的写法是看 y 总的,不是官方的题解,当看到 y 总对于对角线的处理时,真的就一个字:绝!。如果不用这个方法,而用二维数组,真的会慢很多,这么简单的数学方法就提高了一个算法的效率,很让人佩服。所以学好数学很重要,数学对于算法也很重要。