回溯法
算法解释
回溯法是优先遍历搜索的一种特殊情况,又称为试探法。常常用于需要记录节点状态的深度优先搜索。
通常来说,一般对于排列、组合、选择类问题使用这个方法比较方便,也就是说它是采用试错的思想来尝试性的分步解决问题。
具体的,在搜索到某一个节点的时候,如果我们发现目前的节点不是需求的目标的时候,我们需要回退到原来的节点继续搜索,并且把在目前节点修改的状态还原。这样的好处是,我们始终是在原来的总的状态上进行变化,而不用创建新的状态,大大节省了系统空间的开支。
总的来说,就是[修改当前的状态]->[递归]->[还原到修改前的状态],小技巧:需要充分的使用好引用来记录状态的变化。
典型例题讲解
leetcode 46 全排列
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3] 输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> ans;//引用传递,在递归的过程中把满足要求的答案压入
back_track(nums,0,ans);
return ans;
}
void back_track(vector<int>& nums,int level
,vector<vector<int>>&ans)
{
if(level == nums.size()-1)//设立递归的基线条件
{
ans.push_back(nums);
return;
}
for(int i = level;i < nums.size();i++)//不断的后移
{
swap(nums[i],nums[level]);//交换修改状态
back_track(nums,level+1,ans);//递归
swap(nums[i],nums[level]);//还原到修改前的状态,便于下一次新的交换
}
}
};
本题的记录状态的改变就是改变数组的输出,并没有额外的设置visit来记录。
leetcode 79 单词搜索
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
示例 1:
输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]],
word = “ABCCED” 输出:true
class Solution {
public:
vector<int>direction{-1,0,1,0,-1};
bool exist(vector<vector<char>>& board, string word) {
if(board.empty()) return false;
int m = board.size(),n = m>0?board[0].size():0;
vector<vector<bool>> flag(m,vector<bool>(n,false));
bool suc = false;
for(int i = 0;i < m;i++)
{
for(int j = 0;j < n;j++)
{
back_track(board,word,flag,m,n,0,i,j,suc);
}
}
return suc;
}
void back_track(vector<vector<char>>& board,string word
,vector<vector<bool>>&flag,int m,int n,int pos,int i,int j
,bool& suc)
{
//选择一个可以停下来的基线条件
if(i<0||i>=m||j<0||j>=n)
return;
if(flag[i][j]||suc||board[i][j]!=word[pos])
return;
if(pos == word.size()-1)
{
suc = true;
return;
}
flag[i][j] = true;
//向四个方向进行遍历
for(int k = 0;k < 4;k++)
{
back_track(board,word,flag,m,n,pos+1,i+direction[k],
j+direction[k+1],suc);
}
flag[i][j] = false;
}
};
本题是专门设置了visit数组来存储状态。
leetcode 51 N皇后问题
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 给你一个整数 n ,返回所有不同的 n皇后问题 的解决方案。 每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
输入:n = 4
输出:[[".Q…","…Q",“Q…”,"…Q."],["…Q.",“Q…”,"…Q",".Q…"]]
解释:如上图所示,4 皇后问题存在两个不同的解法。
class Solution {
public:
vector<vector<string>> solveNQueens(int n) {
vector<vector<string>> ans;
vector<string> board(n,string(n,'.'));
vector<bool> visit(n,false),ldiag(2*n-1,false),
rdiag(2*n-1,false);
back_track(ans,board,visit,ldiag,rdiag,0,n);
return ans;
}
void back_track(vector<vector<string>>&ans,
vector<string>&board,vector<bool>&visit,vector<bool>&ldiag
,vector<bool>&rdiag,int row,int n)
{
if(row == n)
{
ans.push_back(board);
return;
}
for(int i = 0;i < n ;i++)
{
if(visit[i]||ldiag[row+n-1-i]||rdiag[row+i]){
continue;
}
//更改状态变量
board[row][i] = 'Q';
visit[i]=ldiag[row+n-1-i]=rdiag[row+i]=true;
back_track(ans,board,visit,ldiag,rdiag,row+1,n);
//还原状态变量
board[row][i] = '.';
visit[i]=ldiag[row+n-1-i]=rdiag[row+i]=false;
}
}
};
这道题主要是要考虑多个记录状态的值的变化,而不是之前两道只是考虑单个的某个矩阵或者数组的对应位置的状态变化,一个皇后的摆放位置深刻的改变了多个记录棋盘的状态的数组。还有就是本题记录棋盘的方式非常有趣,是采用字符串的形式来存储的,大大节省了空间的使用。