回溯是递归的副产品,只要有递归就会有回溯。
回溯算法的本质是穷举,穷举所有可能,为了使其高效,会根据条件对其进行剪枝
回溯法解决的问题都可以抽象为树形结构,因为回溯法解决的都是在集合中递归查找⼦集,集合的⼤⼩就构成了树的宽度,递归的深度,都构成的树的深度。
递归就要有终⽌条件,所以必然是⼀颗⾼度有限的树(N叉树)。
回溯算法解决的类型
- 组合问题:N个数⾥⾯按⼀定规则找出k个数的集合
- 切割问题:⼀个字符串按⼀定规则有⼏种切割⽅式
- ⼦集问题:⼀个N个数的集合⾥有多少符合条件的⼦集
- 排列问题:N个数按⼀定规则全排列,有⼏种排列⽅式
- 棋盘问题:N皇后,解数独等等
组合是不强调元素顺序的,排列是强调元素顺序。
回溯模板
逐步确定回溯函数模板
->返回值
->参数
在回溯算法中,一般函数起名字为backtracking,
回溯算法中函数返回值⼀般为void。
针对参数,因为回溯算法需要的参数可不像⼆叉树递归的时候那么容易⼀次性确定下来,所以⼀般是先写逻辑,然后需要什么参数,就填什么参数。
伪代码如下:
void backtracking(参数) {
if (终⽌条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩⼦的数量就是集合的⼤⼩)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
for循环可以理解是横向遍历,即树形结构中的同一层节点,backtracking(递归)就是纵向遍历
集合的⼤⼩构成了树的宽度,递归的深度构成的树的深度
组合问题
切割问题
子集问题
78.子集(子集问题基础模板 无重复 降重由深搜搜索index位置元素)
链接:https://leetcode-cn.com/problems/subsets/
题目
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
用例
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0]
输出:[[],[0]]
思路
子集问题可以看作是寻找树的所有结点
但由于集合是无序的,⼦集{1,2} 和 ⼦集{2,1}是⼀样的,因此需要降重
那么既然是⽆序,取过的元素不会重复取,写回溯算法的时候,for就要从Index开始,⽽不是从0开始!
代码
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
ans.clear();
sonans.clear();
backtraking(nums,0);
return ans;
}
private:
vector<vector<int>> ans;
vector<int> sonans;
void backtraking(vector<int>nums,int index){
for(int i= index;i<nums.size();i++)
{
sonans.push_back(nums[i]);
backtraking(nums,i+1);
sonans.pop_back();
}
ans.push_back(sonans);
}
};
90.子集2(有重复 去重靠判定前后重复)
链接:https://leetcode-cn.com/problems/subsets-ii/
题目
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
用例
示例 1:
输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
示例 2:
输入:nums = [0]
输出:[[],[0]]
思路
本题与子集1的区别主要在于候选集合存在重复,如何去重是本题的关键。
去重,其实就是使用过的元素不能重复选取。
本题的去重的是的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重
代码
class Solution {
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(),nums.end());
ans.clear();
sonans.clear();
backtraink(nums,0);
return ans;
}
private:
vector<vector<int>> ans;
vector<int >sonans;
void backtraink(vector<int>nums,int index)
{
int judge=1;
for(int i=index;i<nums.size();i++)
{
if(judge)
{
sonans.push_back(nums[i]);
backtraink(nums,i+1);
sonans.pop_back();}
if(i+1<nums.size()&&nums[i]==nums[i+1])
{
judge=0;
}else
judge=1;
}
ans.push_back(sonans);
}
};
dalao版本题解
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int startIndex) {
result.push_back(path);
for (int i = startIndex; i < nums.size(); i++) {
// 而我们要对同一树层使用过的元素进行跳过
if (i > startIndex && nums[i] == nums[i - 1] ) { // 注意这里使用i > startIndex
//这个判断条件我在一开始写没想到i》startindex这个条件 只考虑了i必须大于0 导致过不了
continue;
}
path.push_back(nums[i]);
backtracking(nums, i + 1);
path.pop_back();
}
}
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
result.clear();
path.clear();
sort(nums.begin(), nums.end()); // 去重需要排序
backtracking(nums, 0);
return result;
}
};