题1:
指路:LeetCode39 组合总和
思路与代码:
与我们之前写过的组合相似的,在给出的数组中选取元素达到给定目标值。与前者不同的是,本题中的元素可重复,因为元素都是正整数,所以也不存在无限循环。我们用和控制树的深度,当得到的和等于目标值时收集路径并返回以此确定终止条件。 遍历数组中元素并将其收入路径,求和。更改下一层遍历元素的起始位置使其得到的元素集合不相同并回溯以完成单层循环逻辑。代码如下:
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++) {
sum += candidates[i];
path.push_back(candidates[i]);
backtracking(candidates, target, sum, i); // i是因为元素可重复使用
sum -= candidates[i]; // 回溯
path.pop_back();
}
}
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
backtracking(candidates, target, 0, 0);
return result;
}
};
当然,我们可以对其进行剪枝。将给出的数组排序,当其中一条路径和大于给定值时后面的元素也就不用遍历了。
题2:
指路:LeetCode40 组合总和Ⅱ
思路与代码:
对于这个题来说,我们可以以LeetCode39 组合总和为基础,增加去重的操作。注意题意:允许一个组合中有相等的元素(元素可相等但不是重复),但不允许有重复的组合。因此我们将给定数组排序,让相同的元素相邻,例如:[1, 2, 3, 1, 4, 1],排列之后[1, 1, 1, 2, 3, 4],有三个1相邻,这样我们在数组中得到第一个1的全部组合后后两个1就不用遍历了,因为第一个1得到的组合完全包含后两个1得到的组合。再者,同一树枝上元素不能重复使用,我们增加一个bool类型的used数组,记录元素是否被使用过,使用过为1,未使用过为0。代码如下:
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used) {
if (sum > target) return ;
if (sum == target) {
result.push_back(path);
return ;
}
for (int i = startIndex; i < candidates.size(); i++) {
if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == 0)
continue; // 剪枝去重
path.push_back(candidates[i]);
sum += candidates[i];
used[i] = true;
backtracking(candidates, target, sum, i + 1, used);
path.pop_back();
sum -= candidates[i];
used[i] = false;
}
}
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<bool> used(candidates.size(), false);
sort(candidates.begin(), candidates.end());
backtracking(candidates, target, 0, 0, used);
return result;
}
};
题3:
指路:LeetCode131 分割回文串
思路与代码:
我们用startIndex作为给定字符串中字符的分割形式,相似于前面的组合,从startIndex开始,由i向后递增从而增大搜索字串的大小。在单层循环逻辑中定义一个判断回文子串的函数;是回文子串则加入路径,不是回文子串则跳过。于是这就造就了在终止条件方面,我们遇到path中的字串就可以直接加入result结果集而无需判断是否满足回文子串条件。回溯弹出完成单层循环逻辑。最后传参即可。代码如下:
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;
}
};