组合总和
思路:依然一是套用回溯模板,但是我们这里用回溯的是i而不是i+1,因为元素可以重复使用,注意for循环里if(sum(path)<=target)的等号不能少。
class Solution {
public:
vector<int> path;
vector<vector<int>> result;
int sum(vector<int> path){
int s=0;
for(auto i:path)s+=i;
return s;
}
void backtracking(vector<int> c,int target,int start){
if(sum(path)==target){
result.push_back(path);
}else{
for(int i=start;i<c.size();i++){
path.push_back(c[i]);
if(sum(path)<=target){
backtracking(c,target,i);
path.pop_back();
}
else{
path.pop_back();
}
}
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
backtracking(candidates,target,0);
return result;
}
};
但事实上,我们可以利用递归特性来完成总和计算,额外写一个sum函数来计算总和会极大降低我们的效率,尽管思路一致,上下两种写法的速度差了很多。
class Solution {
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<int> state; // 状态(子集)
sort(candidates.begin(), candidates.end()); // 对 candidates 进行排序
int start = 0; // 遍历起始点
vector<vector<int>> res; // 结果列表(子集列表)
backtrack(state, target, candidates, start, res);
return res;
}
private:
void backtrack(vector<int> &state, int target, vector<int> &choices, int start, vector<vector<int>> &res) {
// 子集和等于 target 时,记录解
if (target == 0) {
res.push_back(state);
return;
}
// 遍历所有选择
// 剪枝二:从 start 开始遍历,避免生成重复子集
for (int i = start; i < choices.size(); i++) {
// 剪枝一:若子集和超过 target ,则直接结束循环
// 这是因为数组已排序,后边元素更大,子集和一定超过 target
if (target - choices[i] < 0) {
break;
}
// 尝试:做出选择,更新 target, start
state.push_back(choices[i]);
// 进行下一轮选择
backtrack(state, target - choices[i], choices, i, res);
// 回退:撤销选择,恢复到之前的状态
state.pop_back();
}
}
};
作者:Krahets
链接:https://leetcode.cn/problems/combination-sum/solutions/2363929/39-zu-he-zong-he-hui-su-qing-xi-tu-jie-b-9zx7/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
组合总和II
题目链接:40. 组合总和 II - 力扣(LeetCode)
思路:本来以为是一个简单的回溯题,直到我遇到了
看来这道题对剪枝的要求不低啊。下面的做法就被这个用例卡了。
class Solution {
public:
vector<vector<int>>result;
vector<int>path;
map<vector<int>,int>m;
void backtracking(vector<int>candidates,int target,int start){
if(target==0){
if(!m[path]){
result.push_back(path);
++m[path];
}
}else{
for(int i=start;i<candidates.size();i++){
path.push_back(candidates[i]);
if(target-candidates[i]>=0)
{backtracking(candidates,target-candidates[i],i+1);
path.pop_back();
}else{
path.pop_back();
}
}
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
int sum=0;
for(auto i:candidates)sum+=i;
if(sum<target)return result; //剪枝
sort(candidates.begin(),candidates.end());
backtracking(candidates,target,0);
return result;
}
};
下面看一个剪枝剪得很巧妙的解法,该解法规避了对重复元素的反复递归,因为它将数组排序后,如果发现重复选取同一个数字,则直接结束本轮循环,但本题的答案中又有可能出现相同的元素。其实并不矛盾,因为这个解法里每一次递归后,start发生变化,即使元素重复,但第一个元素又能取到了,因此仍能完成任务。
class Solution {
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<int> state; // 状态(子集)
sort(candidates.begin(), candidates.end()); // 对 candidates 进行排序
int start = 0; // 遍历起始点
vector<vector<int>> res; // 结果列表(子集列表)
backtrack(state, target, candidates, start, res);
return res;
}
private:
void backtrack(vector<int> &state, int target, vector<int> &choices, int start, vector<vector<int>> &res) {
// 子集和等于 target 时,记录解
if (target == 0) {
res.push_back(state);
return;
}
// 遍历所有选择
// 剪枝二:从 start 开始遍历,避免生成重复子集
// 剪枝三:从 start 开始遍历,避免重复选择同一元素
for (int i = start; i < choices.size(); i++) {
// 剪枝一:若子集和超过 target ,则直接结束循环
// 这是因为数组已排序,后边元素更大,子集和一定超过 target
if (target - choices[i] < 0) {
break;
}
// 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过
if (i > start && choices[i] == choices[i - 1]) {
continue;
}
// 尝试:做出选择,更新 target, start
state.push_back(choices[i]);
// 进行下一轮选择
backtrack(state, target - choices[i], choices, i + 1, res);
// 回退:撤销选择,恢复到之前的状态
state.pop_back();
}
}
};
作者:Krahets
链接:https://leetcode.cn/problems/combination-sum-ii/solutions/2363941/40-zu-he-zong-he-iihui-su-qing-xi-tu-jie-7y8s/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
分割回文串
题目链接:131. 分割回文串 - 力扣(LeetCode)
思路:本题投降代码随想录 (programmercarl.com)
class Solution {
private:
vector<vector<string>> result;
vector<string> path; // 放已经回文的子串
void backtracking (const string& s, int startIndex) {
// 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了
if (startIndex >= s.size()) {
result.push_back(path);
return;
}
for (int i = startIndex; i < s.size(); i++) {
if (isPalindrome(s, startIndex, i)) { // 是回文子串
// 获取[startIndex,i]在s中的子串
string str = s.substr(startIndex, i - startIndex + 1);
path.push_back(str);
} else { // 不是回文,跳过
continue;
}
backtracking(s, i + 1); // 寻找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();
backtracking(s, 0);
return result;
}
};
标签:target,组合,int,随想录,start,vector,candidates,path,总和 From: https://www.cnblogs.com/Liubox/p/18029860