[leetcode] Combination Sum

本文探讨了在给定候选数集和目标数时,如何高效地找出所有可能的组合,使得这些组合的元素之和等于目标数。通过分析暴力法的时间复杂度,引入动态规划的概念,最终采用递归和回溯的方法实现算法优化。详细阐述了算法的设计思路、时间复杂度分析及代码实现,旨在提供一种快速解决组合问题的解决方案。

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 (a1a2, … , 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的有效取值 

3)确定边界条件:如果遍历完所有的n层循环之后target为0就说明匹配,遍历完n层循环之后target却不为零说明匹配失败,直接返回。

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]。当然如果你去重的话还是可以减少循环的次数的。


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值