Given a set of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.
The same repeated number may be chosen from C unlimited number of times.
Note:
- All numbers (including target) will be positive integers.
- Elements in a combination (a1, a2, … , ak) must be in non-descending order. (ie, a1 ≤ a2 ≤ … ≤ ak).
- The solution set must not contain duplicate combinations.
For example, given candidate set 2,3,6,7 and target 7,
A solution set is:
[7]
[2, 2, 3]
如果采用暴力法的话,时间复杂度为o(2**n),这是指数爆炸的。第一印象,动态规划,看看行还是不行。如何动态规划呢????????怎么找子问题呢???????怎么找最优子结构呢?????????????伤感啊,发现做动态规划的题目一点感觉也木有啊!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!找资料的时候发现一个不错的链接:http://www.codeguru.com/cpp/cpp/algorithms/combinations/article.php/c15409/Dynamic-Programming-Combination-Sum-Problem.htm,里面讲述了跟这个问题相类似的硬币问题。最后我发现这道题目没有重叠子问题,可以直接使用递归,另外我发现可以使用回溯的方法。
遇到一道题目如果心中没有一个比较好的算法的话,那先想想直接brute force应该如何去解这个题目。这道题目的brute force应该这样,首先考虑只用其中某一个数能否组合成target数,假设C={c1,c2,...,cn}.那么每一个待选的数应该循环Target/ci 次(这里的1<=i<=n);然后考虑用两个数能否组合成target数,假设这两个数是ci和cj,那么需要循环(Target-cj)/ci次(假设有1个ci、2个ci、3个ci等等,剩下的用cj补充,至少有一个cj。)。当由三个数组合成的时候,可以转换为2个数的情况。当由n个数组合成的时候,可以转换为n-1个数的情况。以三个数为例,说明如下:
假设这三个数是ci,cj,ck;先假设有1个ck,那么可以转换两个数ci,cj组合成target-ck的情况,总共需要遍历(target-ci-cj)/ck次。这种brute force的时间复杂度有多高呢?这就跟具体的target还有ci有关了,但是上面的过程转化为递归或者求时间复杂度不太明显,其实从这里可以得到一个等式target=a1*c1+a2*c2+...+an*cn.上面的过程就相当于做一个n层的循环。每一层循环的次数为target/ci次,所以总的时间复杂度为o(target**n/c1*c2*...*cn)。也就是说如果使用brute force的话时间复杂度为o(target**n/c1*c2*...*cn)。这个时间复杂度是否可以接受呢????????另一个问题是如何写n层循环,而且这个n还未知!!!!对不起,这里只能递归了。
首先讲解一下如何使用递归。
1)确定参数:必须有一个参数记录当前遍历到了第几层。必须有一个参数记录C,必须有一个参数记录Target,于是乎函数声明可以初步确定:
void Combination(int dep , int target , vector<int> &candidates);
2)确定函数体:里面必定有一个循环去处理前面公式里面ai的有效取值
4)记录一些关键的值,上面的递归过程只差记录下ai的值了,并生成结果,也就是匹配成功的时候循环里面i的值。
5)补充一些参数,比如说上面有两个变量reval和path可以作为参数来传递,另外必须按引用传递,你懂的
附上代码:
class Solution {
public:
vector<vector<int> > combinationSum(vector<int> &candidates, int target) {
// Start typing your C/C++ solution below
// DO NOT write int main() function
vector<vector<int> > ret;
vector<vector<int> > myret;
vector<int> path(candidates.size(),0);
sort(candidates.begin(),candidates.end());
combination(0,target,candidates,path,myret);
for(int i=0 ; i<myret.size() ; i++){
vector<int> temp;
for(int j=0 ; j<myret[i].size() ; j++){
for(int k=0 ; k<myret[i][j] ; k++){
temp.push_back(candidates[j]);
}
}
ret.push_back(temp);
}
return ret;
}
void combination(int depth, int target, vector<int>& candidates, vector<int>& path, vector<vector<int> >& ret){
if(depth==candidates.size()){
if(target==0)
ret.push_back(vector<int>(path));
return;
}
for(int i=0 ; i<target/candidates[depth]+1 ; i++){
path[depth]=i;
combination(depth+1,target-i*candidates[depth],candidates,path,ret);
}
}
};
这个算法的时间复杂度就是上面所描述的一样,上面就是整个递归的过程,心细一点的话,你会发现这就是回溯!!!!这种方法的时间复杂度略高啊,能不能改进一下时间复杂度???
看了下一题之后发现这里需要补充一个东西,如果C里面含有重复的数的话,对算法的执行不会产生影响,原因很好理解:3个1和1个1加2个1产生的效果是一样的,都是[1,1,1]。当然如果你去重的话还是可以减少循环的次数的。
本文探讨了在给定候选数集和目标数时,如何高效地找出所有可能的组合,使得这些组合的元素之和等于目标数。通过分析暴力法的时间复杂度,引入动态规划的概念,最终采用递归和回溯的方法实现算法优化。详细阐述了算法的设计思路、时间复杂度分析及代码实现,旨在提供一种快速解决组合问题的解决方案。
227

被折叠的 条评论
为什么被折叠?



