系列文章目录
前言
39. 组合总和
Source: 题目
Note:非常有趣的一道题,剪枝很有趣,一定要先进行排序。思考这道题是如何进行去重的?
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例 1:
输入:candidates = [2,3,6,7], target = 7,
所求解集为: [ [7], [2,2,3] ]
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
public:
void backtracing(vector<int>& candidates, int target, int sum, int startIndex) {
if (sum == target) {
// 思考如何结果去重? -自然去重,不会选到重复的组合 -通过for循环
result.push_back(path);
return;
}else if (sum > target) {
return;
}
// 第二部分条件是为了剪枝,若超过则不用进入下一次回溯,前提是要进行排序(前面一群小的数字已经加超过target,)
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target;i++) {
sum += candidates[i];
path.push_back(candidates[i]);
// 注意这里不是i+1,说明在下一层不损失可选数字的数量!!!
// 这里是i而不是startindex,我们希望通过每次for循环来自然减少可选数字
backtracing(candidates, target, sum, i);
sum -= candidates[i];
path.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
// 如果要进行剪枝 则一定要先排序,不排序会出错!
// 因为此时sum + candidates[i]>target不能保证sum + candidates[i+1] > target
sort(candidates.begin(), candidates.end());
backtracing(candidates, target, 0, 0);
return result;
}
};
Tips:
40.组合总和II
Source: 题目
Note:题目给定一个数组和target,要求找到所有数组内元素累加与target相等的子数组,结果集不能重复。
这道题引入了used数组的概念,用于记录使用过的元素,原因是相等的元素可以在纵向遍历中重复使用,而不能在同层中使用(会引发重复的结果)。
另外就是要记住vector排序的方法
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
public:
void backtracing(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used) {
// 结束条件
if (sum == target) {
result.push_back(path);
return;
}else if (sum > target) {
return;
}
// 条件中的剪枝操作,跳过无用的回溯过程(因为从小到大排序,所以可以这么做)
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
// 注意这里是used[i - 1] == false , 为了避免越界 i > 0
// 由于回溯是从上到下有点像dfs,所以纵向遍历的时候used为true,可以继续使用重复元素
// 而同一层的时候used已经被重置为false,这时候不能再用这个元素,因为会出现重复结果组合
// 这个条件需要仔细思考
if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
continue;
}
sum += candidates[i];
path.push_back(candidates[i]);
used[i] = true;
backtracing(candidates, target, sum, i + 1, used);
sum -= candidates[i];
used[i] = false;
path.pop_back();
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
// 需要一个vector记录 当前层是否能继续访问同元素
// 我们不希望在同一层取到同样的数字,因为会导致结果集重复
vector<bool> used(candidates.size(), false);
// 这个sort()排序vector的用法也需要熟记
sort(candidates.begin(), candidates.end());
backtracing(candidates, target, 0, 0, used);
return result;
}
};
Tips:
131.分割回文串
Source: 题目
Note:题目给定一个字符串,要求返回所有的可能的分割,要求分割后的子字符串满足都是回文串
Example 1:
Input: s = “aab”
Output: [[“a”,“a”,“b”],[“aa”,“b”]]
Example 2:
Input: s = “a”
Output: [[“a”]]
class Solution {
private:
vector<vector<string>> result;
vector<string> path;
public:
void backtracing(const string& s, int startIndex) {
if (startIndex >= s.length()) {
result.push_back(path);
return;
}
for (int i = startIndex; i < s.length();i++) {
// 如果从字符串的[startIndex, i]是回文串,那么可以进行回溯
if (isP(s, startIndex, i)) {
// 使用了 substr提取子字符串, 第二个参数为从startIndex 到 i (包含)的字符总数
string str = s.substr(startIndex, i - startIndex + 1);
path.push_back(str);
// i + 1 表示 下一层不重复切割已经切过的部分
backtracing(s, i + 1);
path.pop_back();
}else {
continue;
}
}
}
// 判断是否是回文串, 可以用动态规划
bool isP(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>> partition(string s) {
backtracing(s, 0);
return result;
}
};
Tips:
总结
更加加深了回溯算法的理解