39.组合总数
题目:39. 组合总和 - 力扣(LeetCode)
给你一个 无重复元素 的整数数组
candidates
和一个目标整数target
,找出candidates
中可以使数字和为目标数target
的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates
中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。对于给定的输入,保证和为
target
的不同组合数少于150
个。示例 1:
输入:candidates =[2,3,6,7]
, target =7
输出:[[2,2,3],[7]] 解释: 2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。 7 也是一个候选, 7 = 7 。 仅有这两种组合。
与前面两题不同的是,所以本题挺简单的。自己写出来了。
- 组合没有数量要求
- 元素可无限重复选取
代码
class Solution {
vector<vector<int>>result;
vector<int>path;
void backtraking(vector<int>&candidates,int target,int startindex,int sum)
{
if(sum>target)return;
if(sum==target)
{
result.push_back(path);
return;
}
for(int i=startindex;i<candidates.size();i++)//这里要注意,i的初始值不是0,而是startindex值,反正1就是数组的元素可以重复,不用去重,所以传入的i值不用加1
{
path.push_back(candidates[i]);
sum+=candidates[i];
backtraking(candidates,target,i,sum);
sum-=candidates[i];
path.pop_back();
}
}
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
backtraking(candidates,target,0,0);
return result;
}
};
剪枝优化版本
class Solution {
vector<vector<int>>result;
vector<int>path;
void backtraking(vector<int>&candidates,int target,int startindex,int sum)
{
if(sum==target)
{
result.push_back(path);
return;
}
for(int i=startindex;i<candidates.size()&&sum+candidates[i]<=target;i++)//这里要注意,i的初始值不是0,而是startindex值,反正1就是数组的元素可以重复,不用去重,所以传入的i值不用加1。附加的条件就是剪枝
{
path.push_back(candidates[i]);
sum+=candidates[i];
backtraking(candidates,target,i,sum);
sum-=candidates[i];
path.pop_back();
}
}
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
result.clear();
path.clear();
sort(candidates.begin(),candidates.end());//排序的原因是方便剪枝操作
backtraking(candidates,target,0,0);
return result;
}
};
40.组合总和Ⅱ
题目:40. 组合总和 II - 力扣(LeetCode)
给定一个候选人编号的集合
candidates
和一个目标数target
,找出candidates
中所有可以使数字和为target
的组合。
candidates
中的每个数字在每个组合中只能使用 一次 。注意:解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8, 输出: [ [1,1,6], [1,2,5], [1,7], [2,6] ]
思路
重点是理解去的是哪个重,题中有句“candidates
中的每个数字在每个组合中只能使用 一次 。”。
意思是每个数字只能用一次,但如果两个数字相同也是可以在同一个组合里的。但是!
回看一下题目,元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。
所以我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重。
强调一下,树层去重的话,需要对数组排序!
- used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
- used[i - 1] == false,说明同一树层candidates[i - 1]使用过
- 这里可能有点难理解,但如果等于true的情况,是继续递归下去取另一个数字的情况,属于同一个组合了。而等于false的情况,树已经遍历来到了下一层,有回溯的过程。回溯过了,used会重新变回0
代码(剪枝优化版)
class Solution {
private:
vector<vector<int>>result;
vector<int>path;
void backtraking(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++)
{
// used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
// used[i - 1] == false,说明同一树层candidates[i - 1]使用过
// 要对同一树层使用过的元素进行跳过
if(i>0&&candidates[i]==candidates[i-1]&&used[i-1]==false)continue;//跳过树层,避免出现重复的组合
//i > 0 是为了确保我们访问 candidates[i-1] 不会超出数组的范围。
path.push_back(candidates[i]);
sum+=candidates[i];
used[i]=true;//对同一层的元素,用过就是true
backtraking(candidates,target,sum,i+1,used);
//回溯
path.pop_back();
sum-=candidates[i];
used[i]=false;
}
}
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
result.clear();
path.clear();
vector<bool>used(candidates.size(),false);
sort(candidates.begin(),candidates.end());
backtraking(candidates,target,0,0,used);
return result;
}
};
131.分割回文串
题目:131. 分割回文串 - 力扣(LeetCode)
给你一个字符串
s
,请你将s
分割成一些子串,使每个子串都是回文串
。返回s
所有可能的分割方案。示例 1:
输入:s = "aab" 输出:[["a","a","b"],["aa","b"]]示例 2:
输入:s = "a" 输出:[["a"]]提示:
1 <= s.length <= 16
s
仅由小写英文字母组成
切割问题其实和组合问题思路差不多,所以代码和回溯算法模板差不多。细节上的差别只是判断回文数和切割上。
本题在单层搜索里(切割的时候就判断了)就已经判断是否是回文数了,就不用多此一举在终止条件里判断。
另起一个函数判断回文数,左右指针判断。
代码
class Solution {
private:
vector<vector<string>>result;
vector<string>path;
void backtraking(const string& s,int startindex)
{
if(startindex>=s.size())//找到一种切割方案
{
result.push_back(path);
return;
}
for(int i=startindex;i<s.size();i++)
{
if(isPalindrome(s,startindex,i))//检查是否是回文
{
string str=s.substr(startindex,i-startindex+1);//获取[startIndex,i]在s中的子串
path.push_back(str);
}
else continue;//否则跳过这一层切割
backtraking(s,i+1);
path.pop_back();//回溯,在递归调用结束后,移除最后一个添加的回文子串,以便尝试其他可能的分割
}
}
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;
}
public:
vector<vector<string>> partition(string s) {
result.clear();
path.clear();
backtraking(s,0);
return result;
}
};
- 切割问题可以抽象为组合问题
- 如何模拟那些切割线
- 切割问题中递归如何终止
- 在递归循环中如何截取子串
- 如何判断回文