- 39. 组合总和
- 40.组合总和II
- 131.分割回文串
第一题:组合总和
给你一个 无重复元素 的整数数组 candidates
和一个目标整数 target
,找出 candidates
中可以使数字和为目标数 target
的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
0、思路:数组内的元素可以重复选择,树的深度不受限,直到数组的和为target,然后返回
代码随想录的第二层思想选了a,第二个分支就不考虑a
1、递归三部曲
(1)递归函数参数以及定义变量
定义一个二维数组reslut存放结果集,数组path存放符合条件的结果。递归函数的传进的参数是数组,目标值,求和变量,以及startIndex
vector<vector<int>> result;
vector<int> path;
void backtracking (vector<int> & candidates, int target,int sum,int startIndex)
(2)递归终止条件
从树形图可以看出来,停止的情况一共三种:sum==target;sum>target
if(sum>target){
return;
}
if(sum==target){
result.push_back(path);
return;
}
(2)单层搜索的逻辑
还是从startIndex开始,搜索candidates集合,重点在于可以重复选取的情况
for(int i=startIndex ; i<candidates.size(); i++){
sum+=candidates[i];
path.push_back(candidates[i];
backtracking(candidates,target,sum,i)); //可以重复取元素,所以不用i+1
sum-=candidates[i]; //回溯
path.pop_back(); //回溯
}
第二题:组合总和 2
本题相较于上题需要去重,给的数组里可能存在重复元素,这么看的话这种题好像可以求最优解。
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明: 所有数字(包括目标数)都是正整数。解集不能包含重复的组合。
0、关于去重的问题,假如数组为[1,1,2],有两个1,深度方向上也就是同一树枝方向可以取相同的;但是横向方向也就是同一树层方向不可以取相同元素,因为很可能重复
相比上题需要多一个判断过程,记录同一树枝上的元素是否是使用过,用下面的used来记录
(1)递归函数参数
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target,int sum ,int startIndex,vector<bool> used)
{}
(2)递归终止条件
跟上题一样,要么sum>target;要么sum==target
if(sum>target){
return;}
if(sum==target){
result.push_back(path);
return;
}
(3)单层搜索逻辑
本题重点在于树层去重和树枝去重两方面,树层去重上相邻元素一个元素一个元素的取。
for(int i=startIndex; i<candidates.size() && sum+candidates[i]<=target;i++){
//used[i-1]==true,说明同一树枝candidates[i-1]使用过
//used[i-1]=false,说明同一树层candidates[i-1]使用过
//针对上面的注释,按理来说,只要元素a使用过,它的位置对应的used[i]就会变成true,但是如果发生了回溯,也就是从下一树层回溯到上一树层,true又会变成false,所以方便同树层辨别
if(i>0 && candidates[i]==candidates[i-1] && used[i-1]==false)
{
continue; //遇上同树层重复,直接跳过就行,处理下一元素
}
sum+=candidates[i];
path.push_back(candidates[i]);
used[i]=true;
backtracking(candidates,target,sum,i+1.used);
used[i]=false;
sum-=candidates[i];
path.pop_back();
}
第三题:分割回文串
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
实质和前面的一样,不过求组合变成了将原来的元素切成不同的长度,比如第一种切法结果是{a,a,b} 第二种切法结果是{aa,b} 第三种只有{aab}
1、递归三部曲
(1)定义全局变量和递归函数参数
存放结果的result,存放切割的子串的path,避免重复的startIndex
vector<vector<string>> result;
vector<string> path; // 放已经回文的子串
void backtracking (const string& s, int startIndex) {
(2)定义递归终止条件
分割线其实就是startIndex,也就是数组的起始位置,当起始位置大于s的长度时终止
void backtracking(const string& s,int startIndex){
if(startIndex>=s.size()){
result.push_back(path);
return;}
}
(3)单层搜索的逻辑
定义起始位置startIndex,那么[startIndex,i]就是要截取的子串,每一次递归都要判断这一段子串是否是回文串,是的话将其放入path,如果不是直接跳过
1.用双指针法判断是否是回文串
bool isPalindrome(const string& s, int start, int end) {
for (int i = start, j = end; i < j; i++, j--) {
if (s[i] != s[j]) {
return false;
}
}
return true;
}
2.找到是回文串的字符区间,将他的子串全部求出来,然后放进path中
if (isPalindrome(s, startIndex, i)) { // 是回文子串
// 获取[startIndex,i]在s中的子串
string str = s.substr(startIndex, i - startIndex + 1);
path.push_back(str);
} else { // 不是回文,跳过
continue;
}