一、全排列
1.链接
2.描述

3.思路
在做回溯相关的题目时,一般最优先的是根据题意我画出决策树,然后再去对细节进行设计

4.参考代码
cpp
class Solution
{
public:
vector<int> path;
vector<vector<int>> ret;
bool cheak[7] = {false};
vector<vector<int>> permute(vector<int>& nums)
{
dfs(nums);
return ret;
}
void dfs(vector<int>& nums)
{
if(path.size() == nums.size())
{
ret.push_back(path);
return;
}
for(int i =0;i<nums.size();i++)
{
if(!cheak[i])
{
path.push_back(nums[i]);
cheak[i] = true;
dfs(nums);
//回溯
path.pop_back();
cheak[i] = false;
}
}
}
};
5.代码分析

二、子集
1.链接
2.描述

3.思路
思路一:以每一个数字选或者不选的视角去看

思路二:以每次选几个的视角去看

4.参考代码
思路一:
cpp
class Solution {
vector<vector<int>> ret;
vector<int> path;
public:
vector<vector<int>> subsets(vector<int>& nums)
{
dfs(nums,0);
return ret;
}
void dfs(vector<int>& nums,int pos)
{
if(pos == nums.size())
{
ret.push_back(path);
return;
}
//选
path.push_back(nums[pos]);
dfs(nums,pos+1);
path.pop_back();
//不选
dfs(nums,pos+1);
}
};
思路二:
cpp
class Solution {
vector<vector<int>> ret;
vector<int> path;
public:
vector<vector<int>> subsets(vector<int>& nums)
{
dfs(nums,0);
return ret;
}
void dfs(vector<int>& nums,int pos)
{
ret.push_back(path);
for(int i = pos;i<nums.size();i++)
{
path.push_back(nums[i]);
dfs(nums,i+1);
path.pop_back();
}
}
};
三、找到所有子集的异或总和再求和
1.链接
1863. 找出所有子集的异或总和再求和 - 力扣(LeetCode)
2.描述

3.思路
和上一题的找子集是一样的,题目放这里是为了让我们在学习完上面的两题后,尝试自己动手去做出这题
4.参考代码
cpp
class Solution {
int ret = 0;
int path = 0;
public:
int subsetXORSum(vector<int>& nums)
{
dfs(nums,0);
return ret;
}
void dfs(vector<int>& nums,int pos)
{
ret += path;
for(int i = pos;i<nums.size();i++)
{
path^=nums[i];
dfs(nums,i+1);
path^=nums[i];
}
}
};
四、全排列||
1.链接
2.描述

3.思路
这题的整体思路和第一题全排列一是一样的,不同在于剪枝的不同,因为多了重复的数字,所以需要对如何剪枝进行分析,同样是先画决策树

4.参考代码
cpp
class Solution {
vector<vector<int>> ret;
vector<int> path;
bool cheak[9] = {false};
public:
vector<vector<int>> permuteUnique(vector<int>& nums)
{
sort(nums.begin(),nums.end());
dfs(nums);
return ret;
}
void dfs(vector<int>& nums)
{
if(path.size() == nums.size())
{
ret.push_back(path);
return;
}
for(int i = 0;i<nums.size();i++)
{
if(cheak[i] == false && (i==0 || nums[i-1] != nums[i] || cheak[i-1] == true))
{
path.push_back(nums[i]);
cheak[i] = true;
dfs(nums);
path.pop_back();
cheak[i] = false;
}
}
}
};
五、电话号码的字母组合
1.链接
2.描述

3.思路

4.参考代码
cpp
class Solution {
vector<string> num = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
vector<string> ret;
string path;
public:
vector<string> letterCombinations(string digits)
{
if(digits.size() == 0) return {};
dfs(digits,0);
return ret;
}
void dfs(string digits,int pos)
{
if(path.size() == digits.size())
{
ret.push_back(path);
return;
}
string tmp = num[digits[pos]-'0'];
for(int i = 0 ;i<tmp.size();i++)
{
path.push_back(tmp[i]);
dfs(digits,pos+1);
path.pop_back();
}
}
};
六、括号生成
1.链接
2.描述

3.思路

4.参考代码
cpp
class Solution {
int left = 0;
int right = 0;
vector<string> ret;
string path;
public:
vector<string> generateParenthesis(int n)
{
dfs(n);
return ret;
}
void dfs(int n)
{
if(path.size() == 2*n)
{
ret.push_back(path);
return;
}
if(left < n)
{
path += '(';
left++;
dfs(n);
path.pop_back();
left--;
}
if(right < left)
{
path += ')';
right++;
dfs(n);
path.pop_back();
right--;
}
}
};
七、组合
1.链接
2.描述

3.思路
这是个典型的不重复选择的全排列,思路和前面的题目中基本一致,这里是考察对全排列的控制,当k作为一个参数去限定排列的格子时,我们通过递归出口处去控制格子的数量,不重复因此设计一个pos参数,决策树和代码设计,可以参考上面不重复选择的全排列
4.参考代码
cpp
class Solution {
vector<vector<int>> ret;
vector<int> path;
public:
vector<vector<int>> combine(int n, int k)
{
dfs(n,k,1);
return ret;
}
void dfs(int n,int k,int pos)
{
if(path.size() == k)
{
ret.push_back(path);
return;
}
for(int i = pos;i<=n;i++)
{
path.push_back(i);
dfs(n,k,i+1);
path.pop_back();
}
}
};
八、目标和
1.链接
2.描述

3.思路
每个数字前面,我们可以添加一个正号或者符号,也就是说这个数组中每个数,我们可以选择加上这个数或者减去这个数,最终求得的结果要等于目标值,我们可以将所有情况穷举出来,因此同样是用到回溯算法,每一次可以选择加或者减这个数,当所有数字选完后,判断是否满足情况
代码设计:这是一个典型的不重复选择的全排列穷举,没法剪枝,但是在参数设计上可以有一定的优化,path参数可以选择用整形记录,此时可以将path放到dfs中,利用递归回溯
4.参考代码
cpp
class Solution {
int count = 0;
int aim = 0;
public:
int findTargetSumWays(vector<int>& nums, int target)
{
aim = target;
dfs(nums,0,0);
return count;
}
void dfs(vector<int>& nums,int pos,int path)
{
if(pos == nums.size())
{
if(path == aim)
{
count++;
}
return;
}
//加号
dfs(nums,pos+1,path+nums[pos]);
//减号
dfs(nums,pos+1,path-nums[pos]);
}
};
九、组合总和
1.链接
2.描述

3.思路

4.参考代码
cpp
class Solution {
vector<vector<int>> ret;
vector<int> path;
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target)
{
dfs(candidates,target,0);
return ret;
}
void dfs(vector<int>& candidates,int target,int pos)
{
int sum = 0;
for(auto num:path)
{
sum+=num;
}
if(sum > target) return;
if(sum == target)
{
ret.push_back(path);
return;
}
for(int i = pos;i<candidates.size();i++)
{
path.push_back(candidates[i]);
dfs(candidates,target,i);
path.pop_back();
}
}
};
5.代码分析

总结
本篇从最基础常见的两个回溯全排列的问题去切入,后续整理了大量相关的变形,提供练习,核心就是分析决策树去实现回溯、剪枝和递归出口三个地方的设计,下一篇逐步会再整理大约八九题与回溯相关的题目