1. 算法思想
回溯是一个带有系统性又带有跳跃性的搜索法,其基本思想是,按照深度优先策略从根节点出发,搜索解空间树,当搜索到任意节点时,先判断结点是否包含问题的解,如果不包含,则跳过以该节点为根的子树的搜索,逐层向根节点回溯,并搜索玩根节点的所有子树才结束,而如果只求问题的一个解,则只要搜索到问题的一个解就结束。
2. 应用
2.1 全排列
https://leetcode-cn.com/problems/permutations/
思路分析:从每个数开始一次深度优先遍历,同时创建两个容器和一个变量,两个容器记录路径和已经访问过的数据,变量记录递归的深度,当递归到底(变量等于数组大小)则开始回溯。
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> res;
vector<int> path;
bool* v=new bool[nums.size()]{};
dfs(nums,res,path,v,0);
return res;
}
void dfs(vector<int>& nums,vector<vector<int>>& res,vector<int> path,bool v[],int depth)
{
if(depth==nums.size()){
res.push_back(path);
return;
}
for(int i=0;i<nums.size();i++)
{
if(!v[i])
{
v[i]=true;
path.push_back(nums[i]);
dfs(nums,res,path,v,depth+1);
path.pop_back(); //状态重置,即回溯到之前的状态
v[i]=false;
}
}
}
};
2.2 字符串排列
https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/
思路分析:这题和全排列相比,只需要去重就可以了
class Solution {
public:
vector<string> permutation(string s) {
vector<string> res;
if(s.empty())
return res;
sort(s.begin(),s.end()); //排序使得相同元素相邻
string path;
bool* v=new bool[s.size()]{};
dfs(s,res,path,v,0);
return res;
}
void dfs(string& s,vector<string>& res,string& path,bool v[],int depth)
{
if(depth==s.size()){
res.push_back(path);
return;
}
for(int i=0;i<s.size();i++)
{
if(!v[i])
{
if(i>0&&s[i]==s[i-1]&&v[i-1]) //如果这个元素和上一个一样,就不需要遍历了,
//因为结果会和上一个元素的结果一样
continue;
v[i]=true;
path.push_back(s[i]);
dfs(s,res,path,v,depth+1);
path.pop_back(); //状态重置,即回溯到之前的状态
v[i]=false;
}
}
}
};
2.3 括号生成
https://leetcode-cn.com/problems/generate-parentheses/
思路分析:创建记录路径和结果的数组,左括号任意时刻都可以放,右括号只能在左括号放置的比右括号多的时候。每次递归出来后要回溯到之前的状态。
class Solution {
public:
vector<string> generateParenthesis(int n) {
vector<string> res;
int l=n,r=n; //初始左右括号的剩余数
string path; //记录左右括号的生成路径
dfs(res,l,r,n,path);
return res;
}
void dfs(vector<string>& res,int l,int r,int n,string& path)
{
if(path.size()==2*n){
res.push_back(path);
return;
}
if(l>0){
path+='(';
dfs(res,l-1,r,n,path);
path.pop_back();
}
if(r>l){
path+=')';
dfs(res,l,r-1,n, path);
path.pop_back();
}
}
};
2.4 矩阵中的路径
https://leetcode-cn.com/problems/ju-zhen-zhong-de-lu-jing-lcof/
思路分析:扫描矩阵中的各点,若与word的首字母一致则从这个点开始 dfs
class Solution {
public:
bool exist(vector<vector<char>>& board, string word) {
int row=board.size(),line=board[0].size();
for(int i=0;i<row;i++)
{
for(int j=0;j<line;j++){
if(board[i][j]==word[0]){
vector<vector<bool>> v(row,vector<bool>(line));
v[i][j]=true;
if(dfs(board,i,j,word,v,row,line,0))
return true;
}
}
}
return false;
}
bool dfs(vector<vector<char>>& board,int i,int j,string& word,vector<vector<bool>>& v,int row,int line,int p)
{
if(p==(word.size()-1)&&board[i][j]==word[p])
return true;
if(i+1<row&&!v[i+1][j]&&board[i+1][j]==word[p+1]){
v[i+1][j]=true;
if(dfs(board,i+1,j,word,v,row,line,p+1))
return true;
v[i+1][j]=false;
}
if(j+1<line&&!v[i][j+1]&&board[i][j+1]==word[p+1]){
v[i][j+1]=true;
if(dfs(board,i,j+1,word,v,row,line,p+1))
return true;
v[i][j+1]=false;
}
if(i-1>=0&&!v[i-1][j]&&board[i-1][j]==word[p+1]){
v[i-1][j]=true;
if(dfs(board,i-1,j,word,v,row,line,p+1))
return true;
v[i-1][j]=false;
}
if(j-1>=0&&!v[i][j-1]&&board[i][j-1]==word[p+1]){
v[i][j-1]=true;
if(dfs(board,i,j-1,word,v,row,line,p+1))
return true;
v[i][j-1]=false;
}
return false;
}
};
2.5 剑指 把数字翻译成字符串
https://leetcode-cn.com/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/
思路分析:利用回溯的思想从左向右读取一位或两位数字,将其转换为字符加入string中,同时利用set不能有重复的特性去重,最终res内就是所有的可能排列,它的大小即为所有的翻译方法
class Solution {
public:
int translateNum(int num) {
set<string> res;
string s;
dfs(num,s,res);
return res.size();
}
void dfs(int num,string& s,set<string>& res)
{
if(num==0){
res.insert(s);
return;
}
if((num%100)<26&&(num%100)>9){
s+=num%100+'a';
dfs(num/100,s,res);
s.pop_back();
}
s+=num%10+'a';
dfs(num/10,s,res);
s.pop_back();
}
};
3. 总结
- 回溯算法的思路是一种树形结构,每一次走到头就回退一步
- 回退一步必需要回溯到之前的状态,即必须要将状态重置
- 设置递归的终点,即树形图的叶子结点