第七章 回溯算法part06
1.LeetCode.重新安排行程
先跳过
1.1题目链接:
文章讲解:代码随想录
1.2思路:
1.3附加代码如下所示:
2.LeetCode. N皇后
2.1题目链接:
文章讲解:代码随想录
视频讲解:
2.2思路:都知道n皇后问题是回溯算法解决的经典问题,但是用回溯解决多了组合、切割、子集、排列问题之后,遇到这种二维矩阵还会有点不知所措。
首先来看一下皇后们的约束条件:
不能同行
不能同列
不能同斜线
确定完约束条件,来看看究竟要怎么去搜索皇后们的位置,其实搜索皇后的位置,可以抽象为一棵树。
下面我用一个 3 * 3 的棋盘,将搜索过程抽象为一棵树,如图:
2.3附加代码如下所示:
class Solution {
public:
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;
}
vector<vector<string>> solveNQueens(int n) {
result.clear();
//vector<string>chessboard(n,string(n,'.'));
std::vector<std::string> chessboard(n, std::string(n, '.'));//这是使用标准库函数中的vector
backtracking(n,0,chessboard);
return result;
}
};
3.LeetCode.解数独
3.1题目链接:
文章讲解:代码随想录
视频讲解:
3.2思路:N皇后问题是因为每一行每一列只放一个皇后,只需要一层for循环遍历一行,递归来遍历列,然后一行一列确定皇后的唯一位置。本题就不一样了,本题中棋盘的每一个位置都要放一个数字(而N皇后是一行只放一个皇后),并检查数字是否合法,解数独的树形结构要比N皇后更宽更深。
3.3附加代码如下所示:
class Solution {
public:
bool backtracking(vector<vector<char>>&board)
{
for(int i=0;i<board.size();i++) // 遍历行
{
for(int j=0;j<board[0].size();j++) // 遍历列
{
if(board[i][j]=='.')
{
for(char k='1';k<='9';k++)// (i, j) 这个位置放k是否合适
{
if(isValid(i,j,k,board))
{
board[i][j]=k; // 放置k
if(backtracking(board))return true;// 如果找到合适一组立刻返回
board[i][j]='.'; // 回溯,撤销k
}
}
return false; // 9个数都试完了,都不行,那么就返回false
}
}
}
return true;
}
bool isValid(int row,int col,char val,vector<vector<char>>&board)
{
for(int i=0;i<9;i++)
{
if(board[row][i]==val)
{
return false;
}
}
for(int j=0;j<9;j++)
{
if(board[j][col]==val)
{
return false;
}
}
int startrow=(row/3)*3;
int startcol=(col/3)*3;
for(int i=startrow;i<startrow+3;i++)
{
for(int j=startcol;j<startcol+3;j++)
{
if(board[i][j]==val)
{
return false;
}
}
}
return true;
}
void solveSudoku(vector<vector<char>>& board) {
backtracking(board);
}
};
4.回溯算法总结
代码随想录
**总结:**回溯是递归的副产品,只要有递归就会有回溯,所以回溯法也经常和二叉树遍历,深度优先搜索混在一起,因为这两种方式都是用了递归。
回溯法就是暴力搜索,并不是什么高效的算法,最多再剪枝一下。
回溯算法能解决如下问题:
组合问题:N个数里面按一定规则找出k个数的集合
排列问题:N个数按一定规则全排列,有几种排列方式
切割问题:一个字符串按一定规则有几种切割方式
子集问题:一个N个数的集合里有多少符合条件的子集
棋盘问题:N皇后,解数独等等
回溯法的模板:
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}