目录
对于 39 和 40 两题,有两种不同的防止组合重复的方式。切割回文串不太理解。
39. 组合总和
题目链接:39. 组合总和
给你一个 无重复元素 的整数数组
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 。 仅有这两种组合。示例 2:
输入: candidates = [2,3,5], target = 8 输出: [[2,2,2,2],[2,3,3],[3,5]]示例 3:
输入: candidates = [2], target = 1 输出: []
回溯算法:
-
思考过程:
- 如下树形结构能保证所有的组合都不相同:
- 剪枝:if (sum > target) return; ( 在进入深一层递归前可以进行剪枝,但本文中没有采用。)
-
力扣代码:
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
int sum = 0;
void backstacking(vector<int>& candidates, int target, int i) {
if (sum > target) return;
if(sum == target) {
// 如果在此处对结果去重,会很麻烦,有没有可能在for循环中减少元素重复
result.push_back(path);
return;
}
for(i ; i < candidates.size(); i++) {
path.push_back(candidates[i]);
sum += candidates[i];
backstacking(candidates, target, i); // 参见树形结构,同一个元素可以被重复选取
path.pop_back();
sum -= candidates[i];
}
}
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
backstacking(candidates, target, 0);
return result;
}
};
40.组合总和II
题目链接:40.组合总和II
给定一个候选人编号的集合
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] ]示例 2:
输入: candidates = [2,5,2,1,2], target = 5, 输出: [ [1,2,2], [5] ]
回溯算法:
-
去重思路:
回看一下题目,元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。
所以我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重。
为了理解去重我们来举一个例子:candidates = [1, 1, 2], target = 3(树形结构如下图)(方便起见candidates已经排序了)
强调一下,树层去重的话,需要对数组排序!
-
递归过程1️⃣2️⃣3️⃣:
startIndex = 0 i = 0 candidates[i] = 1
startIndex = 1 i = 1 candidates[i] = 1
startIndex = 2 i = 2 candidates[i] = 2
startIndex = 1 i = 2 candidates[i] = 2 回到上一层后,比较同层的元素有无重复
startIndex = 0 i = 1 candidates[i] = 1
startIndex = 0 i = 2 candidates[i] = 2
-
力扣代码:
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
int sum = 0;
void backstacking(vector<int>& candidates, int target, int startIndex) {
if (sum > target) return;
if(sum == target) {
result.push_back(path);
return;
}
for(int i = startIndex; i < candidates.size(); i++) {
// cout << "startIndex = " << startIndex << " i = " << i << " candidates[i] = " << candidates[i] << endl; // 打印
// 要对同一树层使用过的元素进行跳过
if(i > startIndex && candidates[i] == candidates[i-1]) continue; // 不懂,怎么判断是同一树层???
path.push_back(candidates[i]);
sum += candidates[i];
backstacking(candidates, target, i+1); // 参见树形结构
path.pop_back();
sum -= candidates[i];
}
}
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end()); // 排序
backstacking(candidates, target, 0);
return result;
}
};
131.分割回文串
题目链接:131. 分割回文串
给你一个字符串
s
,请你将s
分割成一些子串,使每个子串都是 回文串 。返回s
所有可能的分割方案。回文串 是正着读和反着读都一样的字符串。
示例 1:
输入:s = "aab" 输出:[["a","a","b"],["aa","b"]]示例 2:
输入:s = "a" 输出:[["a"]]
回溯算法:
-
思考过程:
- 树形结构:
startIndex = 0 i = 0 str = a [0, 0] 子串
startIndex = 1 i = 1 str = a [1, 1]
startIndex = 2 i = 2 str = b [2, 2]
startIndex = 1 i = 2
startIndex = 0 i = 1 str = aa [0, 1]
startIndex = 2 i = 2 str = b [1, 2]
startIndex = 0 i = 2
- startIndex为切割线位置;
- [startIndex, i] 就是要截取的子串;
-
力扣代码:
class Solution {
private:
vector<vector<string>> result;
vector<string> 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]) {
return false;
}
}
return true;
}
void backstacking(const string& s, int startIndex) {
// 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;
}
backstacking(s, i+1);
path.pop_back();
}
}
public:
vector<vector<string>> partition(string s) {
backstacking(s, 0);
return result;
}
};