N皇后问题
方法:递归(推荐使用)
知识点:递归与回溯
递归是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。因此递归过程,最重要的就是查看能不能讲原本的问题分解为更小的子问题,这是使用递归的关键。
如果是线型递归,子问题直接回到父问题不需要回溯,但是如果是树型递归,父问题有很多分支,我需要从子问题回到父问题,进入另一个子问题。因此回溯是指在递归过程中,从某一分支的子问题回到父问题进入父问题的另一子问题分支,因为有时候进入第一个子问题的时候修改过一些变量,因此回溯的时候会要求改回父问题时的样子才能进入第二子问题分支。
n个皇后,不同行不同列,那么肯定棋盘每行都会有一个皇后,每列都会有一个皇后。如果我们确定了第一个皇后的行号与列号,则相当于接下来在n−1行中查找n−1个皇后,这就是一个子问题,因此使用递归:
- 终止条件: 当最后一行都被选择了位置,说明n个皇后位置齐了,增加一种方案数返回。
- 返回值: 每一级要将选中的位置及方案数返回。
- 本级任务: 每一级其实就是在该行选择一列作为该行皇后的位置,遍历所有的列选择一个符合条件的位置加入数组,然后进入下一级。
具体做法:
- step 1:对于第一行,皇后可能出现在该行的任意一列,我们用一个数组pos记录皇后出现的位置,下标为行号,元素值为列号。
- step 2:如果皇后出现在第一列,那么第一行的皇后位置就确定了,接下来递归地在剩余的n−1行中找n−1个皇后的位置。
- step 3:每个子问题检查是否符合条件,我们可以对比所有已经记录的行,对其记录的列号查看与当前行列号的关系:即是否同行、同列或是同一对角线。
代码随想录:
class Solution {
private:
vector<vector<string>> result;
int res = 0;
// n 为输入的棋盘大小
// row 是当前递归到棋盘的第几行了
void backtracking(int n, int row, vector<string>& chessboard) {
if (row == n) {
result.push_back(chessboard);
res++;
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:
int Nqueen(int n) {
result.clear();
res = 0;
std::vector<std::string> chessboard(n, std::string(n, '.'));
backtracking(n, 0, chessboard);
return res;
}
};
牛客代码:
class Solution {
public:
//判断皇后是否符合条件
//所有同一条主对角线的数,其纵坐标减去横坐标再加n,一定是相等的。同理,
//同一条逆对角线上的数字,其横纵坐标之和一定是相等的,根据这个,就可以快速判断主逆对角线上是否有冲突。
bool isValid(vector<int> &pos, int row, int col){
//遍历所有已经记录的行
for(int i = 0; i < row; i++){
//不能同行同列同一斜线
if(row == i || col == pos[i] || abs(row - i) == abs(col - pos[i]))
return false;
}
return true;
}
//递归查找皇后种类
void recursion(int n, int row, vector<int> & pos, int &res){
//完成全部行都选择了位置
if(row == n){
res++;
return;
}
//遍历所有列
for(int i = 0; i < n; i++){
//检查该位置是否符合条件
if(isValid(pos, row, i)){
//加入位置
pos[row] = i;
//递归继续查找
recursion(n, row + 1, pos, res);
}
}
}
int Nqueen(int n) {
int res = 0;
//下标为行号,元素为列号,记录皇后位置
vector<int> pos(n, 0);
//递归
recursion(n, 0, pos, res);
return res;
}
};
BM60.括号生成
题目主要信息:
- 求n对括号的全部合法组合,左右括号之间任意组合,只要合法就行
- 需要输出所有的结果
方法:递归(推荐使用)
思路:
相当于一共n个左括号和n个右括号,可以给我们使用,我们需要依次组装这些括号。每当我们使用一个左括号之后,就剩下n−1个左括号和n个右括号给我们使用,结果拼在使用的左括号之后就行了,因此后者就是一个子问题,可以使用递归:
- 终止条件: 左右括号都使用了n个,将结果加入数组。
- 返回值: 每一级向上一级返回后续组装后的字符串,即子问题中搭配出来的括号序列。
- 本级任务: 每一级就是保证左括号还有剩余的情况下,使用一次左括号进入子问题,或者右括号还有剩余且右括号使用次数少于左括号的情况下使用一次右括号进入子问题。
但是这样递归不能保证括号一定合法,我们需要保证左括号出现的次数比右括号多时我们再使用右括号就一定能保证括号合法了,因此每次需要检查左括号和右括号的使用次数。
1 2 3 4 5 6 7 8 |
|
具体做法:
- step 1:将空串与左右括号各自使用了0个送入递归。
- step 2:若是左右括号都使用了n个,此时就是一种结果。
- step 3:若是左括号数没有到达n个,可以考虑增加左括号,或者右括号数没有到达n个且左括号的使用次数多于右括号就可以增加右括号。
class Solution {
public:
void recursion(int left, int right, string temp, vector<string> &res, int n){
//左右括号都用完了,就加入结果
if(left == n && right == n){
res.push_back(temp);
return;
}
//使用一次左括号
if(left < n)
recursion(left + 1, right, temp + "(", res, n);
//使用右括号个数必须少于左括号
if(right < n && left > right)
recursion(left, right + 1, temp + ")", res, n);
}
vector<string> generateParenthesis(int n) {
//记录结果
vector<string> res;
//记录每次组装的字符串
string temp;
//递归
recursion(0, 0, temp, res, n);
return res;
}
};
BM61.矩阵最长递增路径
题目主要信息:
- 矩阵内是非负数,求最长的递增路径的长度
- 移动方向可以是上下左右,不能超出边界,这将是递归的判定条件
- 同一条路径不能有重复的单元格,需要有记忆
class Solution {
private:
int m = 0;
int n = 0;
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
* 递增路径的最大长度
* @param matrix int整型vector<vector<>> 描述矩阵的每个数
* @return int整型
*/
int solve(vector<vector<int> >& matrix) {
// write code here
int res = 0;//整体最长路径
int lp = 0;//某点开始的最长路径
for (int i = 0; i < matrix.size(); i++) {
for (int j = 0; j < matrix[0].size(); j++) {
recursion(matrix, 1, i, j, lp);
res = max(lp, res);
lp = 0;
}
}
return res;
}
void recursion(vector<vector<int>>& matrix, int len, int i, int j, int& lp) {
lp = max(len, lp);
m = matrix.size();
n = matrix[0].size();
if (i + 1 < m && matrix[i + 1][j] > matrix[i][j]) {
matrix[i][j] = -matrix[i][j];//标记走过的点
recursion(matrix, len + 1, i + 1, j, lp);
matrix[i][j] = -matrix[i][j];//回溯
}
if (i - 1 >= 0 && matrix[i - 1][j] > matrix[i][j]) {
matrix[i][j] = -matrix[i][j];
recursion(matrix, len + 1, i - 1, j, lp);
matrix[i][j] = -matrix[i][j];
}
if (j + 1 < n && matrix[i][j + 1] > matrix[i][j]) {
matrix[i][j] = -matrix[i][j];
recursion(matrix, len + 1, i, j + 1, lp);
matrix[i][j] = -matrix[i][j];
}
if (j - 1 >= 0 && matrix[i][j - 1] > matrix[i][j]) {
matrix[i][j] = -matrix[i][j];
recursion(matrix, len + 1, i, j - 1, lp);
matrix[i][j] = -matrix[i][j];
}
}
};