77. 组合
这道题需要用到回溯算法。我们可以遍历[1,n]中的每一个值,当我们在[1,n]中取了一个值时,我们就利用递归从剩下的集合中再取一个值,反复进行递归操作直到取到的值的数量等于k,然后向上回溯。
class Solution {
public:
//path存储当前搜索到的组合
vector path;
//ans存储符合条件的组合
vector> ans;
vector> combine(int n, int k) {
backtracking(n,k,1);
return ans;
}
//startIndex表示开始搜索的起始位置
void backtracking(int n,int k,int startIndex)
{
if(path.size()==k)
{
//path的大小等于k表示当前组合是符合条件的组合
ans.push_back(path);
return;
}
//从起始位置开始搜索
for(int i=startIndex;i<=n;i++)
{
//将当前搜索的路径放到path中
path.push_back(i);
//继续递归起始位置之后集合
backtracking(n,k,i+1);
//返回上一次递归前弹出当前路径
path.pop_back();
}
}
};
这里我们还可以对回溯进行一些剪枝操作。比如当n=4,k=3时,只有i等于1或2时存在符合条件的组合,当i等于3或4时,剩余的集合数量小于k,就一定没有符合条件的。这里我们就可以重新定义一下k的取值范围。
n-startIndex+1>=k //首先从startIndex到n的元素个数一定要大于等于k
startIndex<=n-k+1 //变化不等式
startIndex+path.size()=i //因为i从startIndex开始,每一个循环i+1,path中的元素也+1
i<=n-k+path.size()+1 //联立上面两个式子就得到i的范围
剪枝后的代码:
class Solution {
public:
//path存储当前搜索到的组合
vector path;
//ans存储符合条件的组合
vector> ans;
vector> combine(int n, int k) {
backtracking(n,k,1);
return ans;
}
//startIndex表示开始搜索的起始位置
void backtracking(int n,int k,int startIndex)
{
if(path.size()==k)
{
//path的大小等于k表示当前组合是符合条件的组合
ans.push_back(path);
return;
}
//从起始位置开始搜索
for(int i=startIndex;i<=n-k+path.size()+1;i++)
{
//将当前搜索的路径放到path中
path.push_back(i);
//继续递归起始位置之后集合
backtracking(n,k,i+1);
//返回上一次递归前弹出当前路径
path.pop_back();
}
}
};
216. 组合总和 III
这题和上一题类似,只需要多加一个判断相等的条件。
class Solution {
public:
int sum=0;
vector path;
vector> ans;
void backtracking(int k,int startIndex,int n)
{
//剪枝操作,大于目标值直接返回
if(sum>n) return;
//比上一题多了一个判断相等的条件
if(path.size()==k && n==sum)
{
ans.push_back(path);
return;
}
for(int i=startIndex;i<=9-k+path.size()+1;i++)
{
path.push_back(i);
sum+=i;
backtracking(k,i+1,n);
sum-=path[path.size()-1];
path.pop_back();
}
}
vector> combinationSum3(int k, int n) {
backtracking(k,1,n);
return ans;
}
};
17. 电话号码的字母组合
这道题的回溯思想非常明显,就是需要不断地从每个数字所代表的字母串中提取字母。所有首先要对数字和字母串之间做一个映射。从全局来看我们需要两层for循环,第一层用来循环数字,第二层循环用来循环字母串。第一层循环的次数就是digits的长度,因为我们要到每个数字对应的字母串中取出一个字母,其实也就是递归的深度。第二层的内容就是单层递归逻辑,包含递归和回溯。
就是取了数字对应的字母串中的一个字母后,递归到下一个数字,再从数字对应的字母串中取一个字母。因为要递归下一个数字,所有我们需要一个形参来表示需要递归的数字。
class Solution {
public:
//首先对号码做个映射
vector ref{
"abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"
};
string path;
vector ans;
void backtracking(string digits,int numIndex)
{
//确定结束条件,path中元素个数等于digits时说明已经取得了满足条件的元素个数
if(path.size()==digits.size())
{
ans.push_back(path);
return;
}
//从第一个数字开始遍历
for(int i=numIndex;i letterCombinations(string digits) {
if(digits.size()==0) return ans;
backtracking(digits,0);
return ans;
}
};
39. 组合总和
这道题有点变化,但是变化不多。题目说同一个数可以无限制的使用。比如说我们取了数值2,那么我们接下来可以从[2,3,6,7]中再取一个值;我们取了3,接下来我们可以从[3,6,7]中再取一个值。所有这么来看仅仅是我们开始取值的位置不是从当前值的下一个开始,而是从当前值开始。
我在做这道题的时候一直在纠结,就是从当前值开始取值会不会导致一直重复取当前值而无法取到后面的值。实际是它是一个for循环,如果当前值不满足它会将当前值弹出后进入下一个for循环。进入下一个循环也就是去取下一个值了,不会陷入只取当前值的情况,并且candidates中的值都不为0。
class Solution {
public:
vector path;
vector> ans;
int sum=0;
void backtracking(vector& candidates,int target,int startIndex)
{
//剪枝操作
if(sum>target) return;
//结束条件
if(sum==target)
{
ans.push_back(path);
return;
}
for(int i=startIndex;i> combinationSum(vector& candidates, int target) {
backtracking(candidates,target,0);
return ans;
}
};