仅用于学习交流,未经许可不可转载。本文转载引用内容仅代表作者本人的观点,与本博客立场无关。
LeetCode39组合总和
代码随想录: 回溯算法——组合总和
力扣39: 组合总和
初印象
- 题目描述:给你一个无重复元素的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。 - 回溯函数返回值与输入参数,二维数组vector<vector> result。输入参数无重复元素数组candidates和目标数target。startIndex和sum。
- 回溯函数终止条件 本题没有元素个数数量限制,当sum>target就return
- 回溯函数遍历过程 在同一个集合内选取组合,需要startIndex。如何避免组合重复?只需要树形结构中本层后面的元素不允许选择本层前面已经选过的元素。重复选取集合中的元素通过在向下递归时backtracking传入的参数i(startIndex)不变来实现。
- 通过对集合candidates排序,可以实现剪枝。如果下一层的sum(sum + candidates[i])已经大于target,结束本轮for循环
剪枝后的树形结构图如下:
来源链接: 代码随想录
看视频讲解后的想法
- 剪枝一般体现在for循环上,所以本题剪枝在for循环上加上一个&&条件,注意是小于等于
- 相应的需要删除backtracking一开始对于和大于target的return操作避免冗余
代码及实现过程中遇到的问题
初始版本
class Solution {
private:
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++) {
path.push_back(candidates[i]);
sum += candidates[i];//不是加i
backtracking(candidates, target, sum, i);//这里是i,以实现重复选取
sum -= candidates[i];
path.pop_back();//这里括号里不能加i
}
}
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
result.clear();
path.clear();
backtracking(candidates, target, 0, 0);
return result;
}
};
- line 14-line17:注意参数,传入的不是i而是candidates[i]
- line16:元素需要/可以重复选取,所以回溯过程中传入的参数不是i+1,而是i
剪枝版本
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {
if (sum == target) {
result.push_back(path);
return;
}
for (int i = startIndex; i <candidates.size()&&sum+candidates[i]<=target; i++) {
path.push_back(candidates[i]);
sum += candidates[i];//不是加i
backtracking(candidates, target, sum, i);//这里是i,以实现重复选取
sum -= candidates[i];
path.pop_back();//这里括号里不能加i
}
}
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
result.clear();
path.clear();
sort(candidates.begin(), candidates.end());//剪枝优化需要排序
backtracking(candidates, target, 0, 0);
return result;
}
};
- line10:从for循环中控制剪枝
- line22:注意sort函数用法
LeetCode40组合总和Ⅱ
代码随想录: 回溯算法——组合总和
力扣40: 组合总和Ⅱ
初印象
- 题目描述:给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。 - 回溯函数返回值与输入参数,二维数组vector<vector> result。输入参数无重复元素数组candidates和目标数target。startIndex和sum。还需要有一个bool数组used实现去重。
- 回溯函数终止条件 本题没有元素个数数量限制,当sum>=target就return
- 回溯函数遍历过程 本题不是重复选取,传参要传i+1。
- 集合(数组candidates)有重复元素,但还不能有重复的组合。如何实现?
- 兄弟去重和子孙去重如何实现
看视频讲解后的想法
- 首先对candidates中的元素进行排序,出现重复组合的原因是,如在组合[1,1,2]中,在选取完第二个1之后,同一层的兄弟节点还有第一个1,即二者值相等。那么之后第二个1的遍历过程,第一个1一定经历过相同的过程。所以要进行兄弟去重。
- 要对兄弟去重和子孙去重进行区别,也即在回溯过程中用的第二个元素而非在向下拓展的过程中用的第二个元素。如在target=4,组合[1,1,2]的例子中。通过used[i-1]=0语句进行区分判断。
- 通过if,continue语句跳过重复情况。
- backtracking过程传入的startIndex是i+1。
代码及实现过程中遇到的问题
剪枝版本
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target, int sum, int startIndex,vector<bool>&used) {
//used有引用符号
if (sum == target) {
result.push_back(path);
return;
}
// used[i - 1] == true,说明父节点candidates[i - 1]使用过
// used[i - 1] == false,说明左边的兄弟节点candidates[i - 1]使用过
// 要对左边的兄弟节点使用过的元素进行跳过
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
//先判断i>0,如果颠倒顺序会不通过
continue;
}
path.push_back(candidates[i]);
sum += candidates[i];//不是加i
used[i] = true;
backtracking(candidates, target, sum, i+1,used);
used[i] = false;
sum -= candidates[i];
path.pop_back();//这里括号里不能加i
}
}
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<bool>used(candidates.size(), false);
result.clear();
path.clear();
sort(candidates.begin(), candidates.end());//剪枝优化需要排序
backtracking(candidates, target, 0, 0,used);
return result;
}
};
- line6:used有引用符号。
- line15:判断条件要注意顺序,否则报错。下面是错误版本的执行结果
上面的代码是可以accept的。
LeetCode131分割回文串
代码随想录: 回溯算法——分割回文串
力扣131: 分割回文串
初印象
- 题目描述:给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。回文串 是正着读和反着读都一样的字符串。
- 使用双指针法判断回文,一个指针从前向后,一个指针从后向前,如果前后指针所指向的元素是相等的,就是回文字符串了。
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;
}
- 回溯函数返回值与输入参数,二维数组vector<vector< string >> result。输入参数原字符串s。int类型startIndex
- 回溯函数终止条件 切割到了字符串最后面
看视频讲解后的想法
- 单层搜索的逻辑,类似于在字符串中放置隔板。把判断回文的步骤放入了单层搜索中。
- 单层搜索for循环中,用[startIndex,i]左闭右闭得区间来表示切割出来的子串。startIndex是左边的“板”,i是不断向后遍历的右边的“板”
代码及实现过程中遇到的问题
剪枝版本
class Solution {
private:
vector<vector<string>> result;//放返回的结果
vector<string> path;//放已经回文的子串
void backtracking(const string& s,int startIndex) {
if (startIndex==s.size()) {
//如果起始位置已经到了字符串末尾,说明已经找到了一组分割方案
result.push_back(path);//本方法在for循环中判断回文而非这里
return;
}
for (int i = startIndex; i < s.size(); i++) {
if (isPalindrome(s,startIndex,i)) {//是回文子串
string str = s.substr(startIndex, i-startIndex+1);
path.push_back(str);
}
else {
continue;//不是回文,跳过,继续for循环
}
backtracking(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();
backtracking(s,0);
return result;
}
};
- line13:substr的使用方法: 大头博客——substr函数的使用。后面的参数是长度
- line17:不是回文要continue。
- line24:<或者<=没有影响都可以ac
上面的代码是可以accept的。
总结
学习了一种used数组判断元素有重复而组合不可重复的判断方法。分割问题与组合问题类似,异曲同工。