依旧是组合问题,只是稍有不同:第一点是集合元素通过数组给出,第二点是元素可以重复,第三点是不限制每个组合的元素个数。针对第一点,用原来的i指针来得到数组中的元素而不是直接作为元素;第二个点,则递归调用的时候从当前元素开始递归而不是原来的下一个元素;第三个点,终止条件上需要补充,当当前路径的和等于目标和的时候就要返回了,这里不能再用path的大小来判断,而应该用当前路径的和与目标和的关系。
void backtracking(vector<int>& candidates,int target,int start)
同样用全局变量path来保存路径,result来保存结果,sum来保存当前路径和。
int sum = 0;
vector<int> path;
vector<vector<int>> result;
if(sum == target){
result.push_back(path);
return;
}
if(sum > target) return;
for (int i = start; i < candidates.size(); i++){
sum += candidates[i];
path.push_back(candidates[i]);
backtracking(candidates,target,i);
sum -= candidates[i];
path.pop_back();
}
注意,其中下一个开始遍历指针时当前的i,而不是下一个i+1,因为每个元素是可以重复的。
完整的代码实现如下:
class Solution {
public:
void backtracking(vector<int>& candidates,int target,int start)
{
if(sum == target){
result.push_back(path);
return;
}
if(sum > target) return;
for (int i = start; i < candidates.size(); i++){
sum += candidates[i];
path.push_back(candidates[i]);
backtracking(candidates,target,i);
sum -= candidates[i];
path.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
backtracking(candidates,target,0);
return result;
}
private:
int sum = 0;
vector<int> path;
vector<vector<int>> result;
};
如果要进行剪枝优化,需要对数组进行从小到大的排列,如果判断出下一次递归后和大于target了,就不再递归了,直接结束,下一次递归后的和为sum + candidates[i]。注意,如果不剪枝,则不需要排序。加入剪枝的代码如下:
class Solution {
public:
void backtracking(vector<int>& candidates,int target,int start)
{
if(sum == target){
result.push_back(path);
return;
}
if(sum > target) return;
for (int i = start; i < candidates.size() && sum + candidates[i] <= target; i++){
sum += candidates[i];
path.push_back(candidates[i]);
backtracking(candidates,target,i);
sum -= candidates[i];
path.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
sort(candidates.begin(),candidates.end());
backtracking(candidates,target,0);
return result;
}
private:
int sum = 0;
vector<int> path;
vector<vector<int>> result;
};
这个题的难点在于怎么去重,因为给定的数组中有重复的元素,如果按照之前的做法,那求得的结果中必定有重复的数组。
组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。没有理解这两个层面上的“使用过” 是造成没有彻底理解去重的根本原因。回看一下题目,元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。所以我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重。
void backtracking(vector<int>& candidates,int target,int startIndex,vector<bool>& used)
另外用全局变量来保存遍历路径和最终的结果,sum来保存路径之和。
int sum = 0;
vector<int> path;
vector<vector<int>> result;
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-1] == candidates[i] && used[i-1] == false) continue; //去重
used[i] = true;
sum += candidates[i];
path.push_back(candidates[i]);
backtracking(candidates,target,i+1,used);
used[i] = false;
sum -= candidates[i];
path.pop_back();
}
完整的代码实现如下:
class Solution {
public:
void backtracking(vector<int>& candidates,int target,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-1] == candidates[i] && used[i-1] == false) continue; //去重
used[i] = true;
sum += candidates[i];
path.push_back(candidates[i]);
backtracking(candidates,target,i+1,used);
used[i] = false;
sum -= candidates[i];
path.pop_back();
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<bool> used(candidates.size(),false);
sort(candidates.begin(),candidates.end());
backtracking(candidates,target,0,used);
return result;
}
private:
int sum = 0;
vector<int> path;
vector<vector<int>> result;
};
注意,需要对给定的数组进行排序。另外,也可以像上一个题一样进行剪枝优化。
和之前的组合一样,还是要用for循环加递归,用for循环遍历树形结构的宽度,递归遍历树形结构的深度,for–>i,递归–>startIndex.
vector<string> path;
vector<vector<string>> result;
void backtracking(string& s,int startIndex);
if(startIndex == s.size()){
result.push_back(path);
return;
}
首先判断这个子串是不是回文,如果是回文,就加入在vector path中,path用来记录切割过的回文子串。
for(int i = startIndex; i < s.size();i++){
string str = s.substr(startIndex,i-startIndex+1);
if(isVaild(str)) path.push_back(str);
else continue;
backtracking(s,i+1);
path.pop_back();
}
其中判断是否为回文串用双指针来实现,一个指针从前开始,一个指针从后开始,两个指针向中间移动,代码如下:
bool isVaild(string& str){ //判断是否为回文串
int left = 0, right = str.size()-1;
while(left < right){
if(str[left] != str[right]) return false;
left++,right--;
}
return true;
}
完整代码如下:
class Solution {
public:
bool isVaild(string& str){ //判断是否为回文串
int left = 0, right = str.size()-1;
while(left < right){
if(str[left] != str[right]) return false;
left++,right--;
}
return true;
}
void backtracking(string& s,int startIndex){
if(startIndex == s.size()){
result.push_back(path);
return;
}
for(int i = startIndex; i < s.size();i++){
string str = s.substr(startIndex,i-startIndex+1);
if(isVaild(str)) path.push_back(str);
else continue;
backtracking(s,i+1);
path.pop_back();
}
}
vector<vector<string>> partition(string s) {
backtracking(s,0);
return result;
}
private:
vector<string> path;
vector<vector<string>> result;
};