39. 组合总和
思路:本题可以存在重复元素,但是答案中具有相同元素,可以顺序不同,也视为同一元素。因此,要让startIndex等于当前答案遍历的起始元素。
class Solution {
public:
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();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
result.clear();
path.clear();
int sum = 0;
backtracking(candidates, target, sum, 0);
return result;
}
};
40.组合总和II
误区:题目要求candidates
中的每个数字在每个组合中只能使用 一次 ,但是由于有重复的数字,设置startIndex并不能完全解决这个问题。
此时需要进行排序后去重。
由于回溯分为两部分:for循环遍历(横向),递归遍历(纵向)。纵向遍历是遍历除去当前值后的后面的元素组成的数组,不断重复。
如果原始数组中元素都不重复,那么就可以直接用startIndex进行纵向遍历去重,避免重复的答案集合;
如果原始数组中的元素有重复,那么就需要排序后进行横向遍历去重,跳过原始集合中重复的元素。
这里使用startIndex去重,在纵向遍历过程,递归时传入startIndex,利用startIndex更新candidates[i]的值。在for循环遍历中,利用i++更新candidates[i]的值,此时更新完后,如果candidates[i] == candidates[i-1],说明相邻元素是相同的,而在candidates[i-1]递归的时候,已经找到符合题目要求的元素组合了,因此candidates[i]不用再次进行递归,因此就利用continue跳过本次循环,从而避免产生重复的答案。
class Solution {
private:
vector<vector<int>>result; //存放结果
vector<int>path;
void backtracking(vector<int>& candidates, int target, int startIndex, int sum){
//if(sum > target)return;
if(sum == target){
result.push_back(path);
return;
}
for(int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++){
if(i > startIndex && candidates[i] == candidates[i-1]){
continue;
}
sum += candidates[i];
path.push_back(candidates[i]);
backtracking(candidates, target, i + 1, sum); //递归
sum -= candidates[i]; //回溯
path.pop_back(); // 回溯
}
}
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
result.clear();
path.clear();
int sum = 0;
sort(candidates.begin(), candidates.end());
backtracking(candidates, target, 0, sum);
return result;
}
};
131.分割回文串
回文串:指正向读和反向读都相同的字符串。无论是单词或者短语,如果它在忽略空格、标点符号和字母大小写的情况下从前往后读和从后往前读是一致的,那么它就是一个回文串。
本题是要找到几种分割方式,使得所有被分割的子串都是回文串。
因此终止条件为
if(startIndex >= s.size()){ result.push_back(path); return; }
这意味着一种分割方式的结束。
下面语句是将利用startIndex和i在每层递归遍历中将子串提取出来,并判断该子串是否是回文串。
string str = s.substr(startIndex, i - startIndex + 1);
class Solution {
private:
vector<vector<string>> result;
vector<string>path;
bool isPalindrome(const string& s, int left, int right){
while(left <= right){
if(s[left] != s[right]){
return false;
}
left++;
right--;
}
return true;
}
void backtracking(const string& s, int startIndex){
if(startIndex >= s.size()){
result.push_back(path);
return;
}
//starIndex就是分割线
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); // 若字符串是回文串,那么将其放入path中
}else{
continue;
}
backtracking(s, i + 1);
path.pop_back();
}
}
public:
vector<vector<string>> partition(string s) {
result.clear();
path.clear();
backtracking(s, 0);
return result;
}
};