39.组合总和
题目: 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。
链接: https://leetcode.cn/problems/combination-sum
思路:
vector<vector<int>> res
存放满足条件得结果的集合
vector<int> path
存放满足条件的结果
startIndex
确定每次遍历的初始位置
sum
记录当前路径的和- 遍历这个数组
- 回溯三部曲
①确定传入参数和返回值
②确定终止条件
sum == target
③单层循环的逻辑
if(sum == target){
res.push_back(path);
return;
}
for(int i = startIndex; i < candidates.size(); ++i){
path.push_back(candidates[i]);
sum += candidates[i];
backtracking(candidates, target, i, sum);
sum -= candidates[i];
path.pop_back();
}
class Solution {
private:
vector<vector<int>> res;
vector<int> path;
void backtracking(vector<int>& candidates, int target, int startIndex, int sum){
if(sum > target){
return;
}
if(sum == target){
res.push_back(path);
return;
}
for(int i = startIndex; i < candidates.size(); ++i){
path.push_back(candidates[i]);
sum += candidates[i];
backtracking(candidates, target, i, sum);
sum -= candidates[i];
path.pop_back();
}
}
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
backtracking(candidates, target, 0, 0);
return res;
}
};
剪枝操作:
对总集合排序之后,如果下一层的sum(就是本层的 sum + candidates[i])已经大于target,就可以结束本轮for循环的遍历。
40.组合总和II
题目: 给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
链接: https://leetcode.cn/problems/combination-sum-ii
思路:
vector<int> path
用来存放满足条件的组合
vector<vector<int>> res
用来存放满足条件的组合的集合
int sum
用来记录当前路径的和
int startIndex
用来确定遍历起始位置(包含去重逻辑)
vector<bool> used
用来存放元素的遍历情况(是否被遍历过)- 回溯三部曲
①确定参数和返回值
void trackingback(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used);
②确定终止条件
sum == target
,将path
存入res
中,然后return
sum > target
,直接return
③单层循环的逻辑
void trackingback(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used){
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 - 1] == candidates[i] && used[i - 1] == false){
continue;
}
sum += candidates[i];
path.push_back(candidates[i]);
used[i] = true;
trackingback(candidates, target, sum, i + 1, used);
//回溯
used[i] = false;
path.pop_back();
sum -= candidates[i];
}
}
- 其他操作:对
candidates
里面的元素在一开始进行排序
class Solution {
private:
vector<vector<int>> res;
vector<int> path;
void trackingback(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used){
if(sum > target){
return;
}
if(sum == target){
res.push_back(path);
return;
}
for(int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; ++i){
if(i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false){
continue;
}
sum += candidates[i];
used[i] = true;
path.push_back(candidates[i]);
trackingback(candidates, target, sum, i + 1, used);
sum -= candidates[i];
used[i] = false;
path.pop_back();
}
}
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<bool> used(candidates.size());
int sum;
sort(candidates.begin(), candidates.end());
trackingback(candidates, target, sum, 0, used);
return res;
}
};
131.分割回文串
题目: 给你一个字符串 s
,请你将 s
分割成一些子串,使每个子串都是 回文串 。返回 s
所有可能的分割方案。回文串 是正着读和反着读都一样的字符串。
链接: https://leetcode.cn/problems/palindrome-partitioning/
思路:
vector<vector<char>> res
存放满足条件的结果的集合
vector<char> path
存放满足条件得结果- 判断字符串是否为回文字符串
bool isPalindrome(const string& s, int start, int end){
for(int i = start, j = end; i < j; ++i, --j){
if(s[i] == s[j]){
continue;
}
else{
return false;
}
}
return true;
}
- 回溯法三部曲
①确定参数和返回值
int startIndex
单次遍历的起点
void backtracking(const string& s, int startIndex);
②确定终止条件
startIndex
遍历到了字符串s
末尾s.size()
if(startIndex >= s.size()){
res.push_back(path);
return;
}
③单层遍历的逻辑
void backtracking(const string& s, int startIndex){
if(startIndex >= s.size()){
res.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);
path.push_back(str);
}else{
continue;
}
backtracking(s, i + 1);
path.pop_back();
}
}
优化: 如何更高效的计算一个子字符串是否是回文字串。使用动态规划可以高效地事先一次性计算出, 针对一个字符串s
, 它的任何子串是否是回文字串, 然后在我们的回溯函数中直接查询即可, 省去了双指针移动判定这一步骤。