1.216组合总和三
思路:首先来说想到回溯算法可以解决组合问题,所以首先想到的就是一个回溯算法,按照回溯三部曲进行操作。其次看到里面的只能使用0-9和每个只能使用一次,所以可以对其进行剪枝操作减少对应的工作量。即确定当前到9的数的个数总和是否满足条件。
剪枝:就是根据自己的一些提前的预处理,确定哪些一定是不能满足的条件,从而减少相应的一个工作量。
class Solution {
public:
void backtracking(vector<vector<int>>& result,vector<int>& path,int k,int n,int startIndex,int sum){
if(sum == n){
if(path.size() == k){
result.push_back(path);
return;
}
return;
}
if(sum>n){
return;
}
for(int i = startIndex;i<=n&&i<=9-(k-path.size())+1;i++){//规定了范围,而且每个数只能使用一次
path.push_back(i);
sum += i;
backtracking(result,path,k,n,i+1,sum);
sum -= i;
path.pop_back();
}
return;
}
vector<vector<int>> combinationSum3(int k, int n) {
vector<vector<int>> result;
vector<int> path;
backtracking(result,path,k,n,1,0);
return result;
}
};
2.17电话号码的字母组合
组合问题:使用回溯算法来进行解决。其次是一个用一个const string的数组做一个匹配。根据当前的值来确定这个的字符,然后进行相应的一个操作。终止条件:就是确定长度相等添加。其中比较难想到的可能就是一个创建对应的数组和确定对应的字符串。
class Solution {
public:
const string callnum[8] = {"abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
void backtracing(vector<string>& result,string& path,string digits,int startIndex){
if(path.size() == digits.size()){
result.push_back(path);
return;
}
int digit = digits[startIndex]-'2';//确定对应的数
string letter = callnum[digit];
for(int i = 0;i<letter.size();i++){
path.push_back(letter[i]);
backtracing(result,path,digits,startIndex+1);
path.pop_back();
}
return;
}
vector<string> letterCombinations(string digits) {
vector<string> result;
string path;
if(digits.size()==0)return result;//这里要把这种情况给单独提出来,因为如果不提出来的话,返回的是一个[""]而不是[]
backtracing(result,path,digits,0);
return result;
}
};
3.39组合总和
这个整体来说就是可以参考组合总和三。不过需要考虑的是这个地方的数可以无限重复,所以在这个地方我们输入的startIndex是i而不是组合总和的i+1。从而确定其可以无限重复。
class Solution {
public:
void backtracking(vector<vector<int>>& result,vector<int> path,vector<int>& candidates,int target,int sum,int startIndex){
if(sum == target){
result.push_back(path);
return;
}
if(sum>target){
return;
}
for(int i = startIndex;i<candidates.size();i++){
path.push_back(candidates[i]);
sum += candidates[i];
backtracking(result,path,candidates,target,sum,i);
sum -= candidates[i];
path.pop_back();
}
return;
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> result;
vector<int> path;
backtracking(result,path,candidates,target,0,0);
return result;
}
};
4.40组合总和二
首先确定要使用回溯算法,然后在这个里面我们首先要对candidate进行一个排序,这样可以保证数组是一个递增的,方便后序的操作。这个地方回溯算法的难点在于如何跳过重复的,即剪枝。比如说我们要求和为6,此时的数组为1 1 1 5。我们如何将后面的那两个给去除呢。第一种方法是使用set,但是太繁琐了,操作完成之后还得复制一份给新的相对来说不好,而且这样就是一个暴力的递归,花费的时间也会比较多。所以我们使用第二种方法。
难点:使用used的辅助数组,这个辅助数组可以帮助我们确定前一个数是否被使用过了,如果使用过,且当前值和前一个数相等,那么就可以直接跳过这个了。
class Solution {
public:
void backtracking(vector<vector<int>>& result,vector<int> path,vector<int>& candidates,int target,int sum,int startIndex,vector<bool>& used){
if(sum == target){
result.push_back(path);
return;
}
if(sum>target){
return;
}
for(int i = startIndex;i<candidates.size();i++){
if(i>0&&candidates[i] == candidates[i-1]&&used[i-1] == false){
continue;
}
path.push_back(candidates[i]);
sum += candidates[i];
used[i] = true;
backtracking(result,path,candidates,target,sum,i+1,used);
used[i] = false;
sum -= candidates[i];
path.pop_back();
}
return;
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(),candidates.end());
int n = candidates.size();
vector<bool> used(n,false);
vector<vector<int>> result;
vector<int> path;
backtracking(result,path,candidates,target,0,0,used);
return result;
}
};
这类组合问题,基本上就是result,path,startindex作为最基础的输入参数,还有一些就需要根据实际的来进行改进了。