回溯专题
1.DFS 和回溯算法区别
DFS 是一个劲的往某一个方向搜索,而回溯算法建立在 DFS 基础之上的,但不同的是在搜索过程中,达到结束条件后,恢复状态,回溯上一层,再次搜索。因此回溯算法与 DFS 的区别就是有无状态重置
2.何时使用回溯算法
当问题需要 “回头”,以此来查找出所有的解的时候,使用回溯算法。即满足结束条件或者发现不是正确路径的时候(走不通),要撤销选择,回退到上一个状态,继续尝试,直到找出所有解为止
3.怎么样写回溯算法(从上而下,※代表难点,根据题目而变化)
①画出递归树,找到状态变量(回溯函数的参数),这一步非常重要※
②根据题意,确立结束条件
③找准选择列表(与函数参数相关),与第一步紧密关联※
④判断是否需要剪枝
⑤作出选择,递归调用,进入下一层
⑥撤销选择
4.回溯问题的类型
这里先给出,我总结的回溯问题类型,并给出相应的 leetcode题目(一直更新),然后再说如何去编写。特别关注搜索类型的,搜索类的搞懂,你就真的搞懂回溯算法了,,是前面两类是基础,帮助你培养思维
类型 题目链接
子集、组合 子集、子集 II、组合、组合总和、组合总和 II
全排列 全排列、全排列 II、字符串的全排列、字母大小写全排列
搜索 解数独、单词搜索、N皇后、分割回文串、二进制手表
注意:子集、组合与排列是不同性质的概念。子集、组合是无关顺序的,而排列是和元素顺序有关的,如 [1,2] 和 [2,1] 是同一个组合(子集),但 [1,2] 和 [2,1] 是两种不一样的排列!!!!因此被分为两类问题
【LeetCode 90】
采用回溯法,要先排序必须,防止重复,另外还需要判重
class Solution {
public:
vector<vector<int>> ret;
void dfs(vector<int> nums,vector<int> path,int start)
{
ret.push_back(path);
for(int i=start;i<nums.size();i++)
{
cout<<i<<endl;
if(i>start&&nums[i]==nums[i-1])
{
continue;
}
path.push_back(nums[i]);
dfs(nums,path,i+1);
path.pop_back();
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(),nums.end());
vector<int> path;
dfs(nums,path,0);
return ret;
}
};
子集!=排列
子集是没有顺序的,排列是有顺序的
如果我们固定了(选择了)某个数,那么他的下一层的选择列表就是——除去这个数以外的其他数!!\比如,第一次选择了2,那么他的下一层的选择列表只有1和3;如果选择了3,那么他的下一层的选择列表只有1和2,那么这个时候就要引入一个used数组来记录使用过的数字
【LeetCode 46】
class Solution {
public:
vector<vector<int>> ret;
vector<int> path;
void dfs(vector<int> nums,vector<int> path,int used[])
{
if(path.size()==nums.size())
{
ret.push_back(path);
return;
}
for(int i=0;i<nums.size();i++)
{
if(used[i]==0)
{
path.push_back(nums[i]);
used[i]=1;
dfs(nums,path,used);
used[i]=0;
path.pop_back();
}
}
}
vector<vector<int>> permute(vector<int>& nums) {
int used[nums.size()];
memset(used,0,sizeof(used));
dfs(nums,path,used);
return ret;
}
};