5.组合总和
给你一个 无重复元素 的整数数组
candidates
和一个目标整数target
,找出candidates
中可以使数字和为目标数target
的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates
中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。对于给定的输入,保证和为
target
的不同组合数少于150
个。
对比一下:
找出所有相加之和为
n
的k
个数的组合,且满足下列条件:
只使用数字1到9
每个数字 最多使用一次
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
其实我觉得这两题挺像的,最大的区别就是第五题并没有k的限制,所以深度并未被限制
以及元素可以重复选用,所以backtracking(target,candidates,i)传入的应该是i而不是i+1,这样才能保证结果中可以出现重复元素的结果
cpp
class Solution {
public:
vector<vector<int>>result;
vector<int>path;
void backtracking(vector<int>&candidates,int target,int sum,int startIndex)
{
//终止条件
if(sum>target)//一定加上这个判断,不然就会栈溢出
{
return;
}
if(sum==target)
{
result.push_back(path);
return;
}
for(int i = startIndex;i<candidates.size();i++)//注意本题的i是坐标
{
sum+=candidates[i];
path.push_back(candidates[i]);
backtracking(candidates,target,sum,i);
path.pop_back();
sum-=candidates[i];
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
result.clear();
path.clear();
backtracking(candidates,target,0,0);//startIndex在本题传入的是下标,所以从0开始
return result;
}
};
6.组合总和II
给定一个候选人编号的集合
candidates
和一个目标数target
,找出candidates
中所有可以使数字和为target
的组合。
candidates
中的每个数字在每个组合中只能使用 一次 。**注意:**解集不能包含重复的组合。
这题更像了
找出所有相加之和为
n
的k
个数的组合,且满足下列条件:
只使用数字1到9
每个数字 最多使用一次
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
最大的区别就是去重!
要求是不可在一个组合中使用用一个元素,但是可以用值相等的不同元素!
首先,我们在给backtracking传入数组时,我们不是传入原来的数组,而是将排序过后的数组传入 为什么呢? 示例:输入: candidates = [10,1,2,7,6,1,5], target = 8, 输出: [ [1,1,6], [1,2,5], [1,7], [2,6] ] 如果直接操作原数组,那么在取第一个1的时候,我们会得到一次[1,7],然后在取第二个1的时候,又会得到一次[1,7]就重复了 但是如果我们将数组进行排序,并引入一个bool类型的used数组,这个数组的长度与数组相同,每个位置的false代表这个位置对应的元素已经使用过,如果是true说明这个元素正在使用 排序后的数组:[1,1,2,5,6,7,10]起到将相邻的元素放到一起的作用 然后传入 backtracking(),当循环到第一个1时,将数组used[i]设为true,代表正在历经第一个1能组成的所有结果,遍历完就把used设回false 再循环到第二个1时,通过判断candidates[i]是否与candidates[i-1]相等 && used[i-1]是否为false,如果相等并且used[i-1]=false说明当前这个和前者相同的元素已经遍历完了所有它的可能值,不要再循环了
难点讲解完毕,上代码
cpp
class Solution {
public:
vector<vector<int>>result;
vector<int>path;
void backtracking(vector<int>&candidates,int target,int sum,int startIndex,vector<bool>& used)
{
//终止条件
if(sum==target)
{
result.push_back(path);
return;
}
for(int i=startIndex;i<candidates.size()&& sum+candidates[i]<=target;i++)
{//注意:sum+candidates[i]<=target是剪枝操作
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;//用过了
path.pop_back();//回溯
sum-=candidates[i];//
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<bool>used(candidates.size(),false);//设置used数组来管理使用过的元素
result.clear();
path.clear();
sort(candidates.begin(),candidates.end());//难点,为什么要给数组排序
backtracking(candidates,target,0,0,used);
return result;
}
};
7.分割回文子串
给你一个字符串
s
,请你将s
分割成一些子串,使每个子串都是回文串
。返回
s
所有可能的分割方案。示例 1:
输入:s = "aab" 输出:[["a","a","b"],["aa","b"]]
示例 2:
输入:s = "a" 输出:[["a"]]
上图是根据给的示例做的分割,不理解的话建议看卡哥的视频讲解,听完就感觉这题很简单了
套用回溯算法模板
cpp
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
cpp
class Solution {
public:
vector<string>path;//存储每一组回文串的集合
vector<vector<string>>result;//二维数组,存储最终结果
bool isHuiWen(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;
}
void backtracking(const string& s,int startIndex)//startIndex起到切割线的作用
{//终止条件
if(startIndex >= s.size())//说明已经切割到字符串最后
{
result.push_back(path);
return;
}
for(int i = startIndex;i<s.size();i++)
{//startIndex是不变的,但是i是不断增加的
//难点:要想到边循环边判断回文
if(isHuiWen(s,startIndex,i)==true){
//取子串
string str = s.substr(startIndex,i-startIndex+1);
path.push_back(str);
}else{
continue;
}
backtracking(s,i+1);
path.pop_back();//回溯弹出
}
}
vector<vector<string>> partition(string s) {
result.clear();
path.clear();
backtracking(s,0);
return result;
}
};
时间复杂度O(n²)