[LeetCode] Combination Sum、Combination Sum II

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] 

搜索类型的题目,注意剪枝就好了,我剪的地方是看当前数最多还能放几个。注意有可能放K个当前数,但是再加上后面个数就超了,很明显放K+1,K+2个数的时候肯定也是超的,所以在这里要剪枝,但是也有可能只放M个当前数刚刚好,不需要后面的数,所以要拎出来单独讨论,以方便后面剪枝。

class Solution {
public:
    vector<vector<int> > combinationSum(vector<int> &can, int tar) {
		// Start typing your C/C++ solution below
		// DO NOT write int main() function
        sort(can.begin(),can.end());
		vector<vector<int> > ans;
		vector<int> num;
		int sum=0;
		solve(ans,can,num,0,sum,tar);
		return ans;
	}
	void solve(vector<vector<int> >& ans,vector<int>& can,vector<int>& num,int k,int sum,int tar)
	{
		int n =can.size();
		if ( k>=n|| sum>tar || sum+can[k]>tar )
			return;
		if ( (tar-sum)%can[k]==0 )
		{
			int tn=(tar-sum)/can[k];
			for(int t=0;t<tn;t++)
				num.push_back(can[k]);
			ans.push_back(num);
			for(int t=0;t<tn;t++)
				num.pop_back();
		}
		int maxN=(tar-sum)/can[k]-1;
		solve(ans,can,num,k+1,sum,tar);
		int i=0;;
		while(i<maxN)
		{
			num.push_back(can[k]);
			i++;
			sum+=can[k];
			if ( sum == tar )
				ans.push_back(num);
			if (k>=n-1|| sum+can[k+1]>tar)
				break;
			solve(ans,can,num,k+1,sum,tar);
		}
		while(i--)
			num.pop_back();
	}
};


第二种思路呢,就是很明显是个多重背包的问题,解多重背包是很容易的,麻烦的是要求路径,所以求路径还是要用DFS搜索。

结果由于DFS的原因,貌似时间并没有比上面的直接搜索要快一些,但也有可能是剪枝剪得不好啦。

class dpSolution {
public:
	vector<vector<int> > combinationSum(vector<int> &can, int tar) {
		// Start typing your C/C++ solution below
		// DO NOT write int main() function

		vector<vector<int> > ans;
		int n = can.size();
		sort(can.begin(),can.end());
		int m=n;
		for(int i=0;i<n;i++)
		{
			if(can[i]>tar)
			{
				m=i;
				break;
			}
		}
		if ( m==0 ) return ans;
		vector<vector<int> > dp(m,vector<int>(tar+1,0));
		int tmp=0;
		while(tmp<=tar)
		{
			dp[0][tmp]=1;
			tmp+=can[0];
		}
		for(int i=1;i<m;i++)
		{
			for(int j=0;j<can[i];j++)
				dp[i][j]=dp[i-1][j];
			for(int j=can[i];j<tar+1;j++)
			{
				if ( j%can[i]==0 )
					dp[i][j]=1;
				else
					dp[i][j]=dp[i][j-can[i]]||dp[i-1][j];
			}
		}

		vector<int> nums;
		dfs(m-1,tar,dp,ans,nums,can);
		for(int i=0;i<ans.size();i++)
			reverse(ans[i].begin(),ans[i].end());
		return ans;
	}
	void dfs(int k,int sum,vector<vector<int> >& dp,vector<vector<int> >& ans,vector<int>& nums,vector<int>& can)
	{
		if ( k<0 || sum<=0 )
			return;
		if ( dp[k][sum]== 0 )
			return;
		int tSum=sum;
		int in=0;
		while(tSum>0)
		{
			if ( dp[k][tSum]!=1 ) 
				break;
			nums.push_back(can[k]);
			in++;
			tSum-=can[k];
			if ( tSum==0 )
				ans.push_back(nums);
			dfs(k-1,tSum,dp,ans,nums,can);
		}
		while(in--)
			nums.pop_back();
		dfs(k-1,sum,dp,ans,nums,can);
	}
};


之前这个多重背包写得好丑。。。新写一遍的好看多了。

class Solution {
public:
    vector<vector<int> > combinationSum(vector<int> &can, int target) {
        // Note: The Solution object is instantiated only once and is reused by each test case.
        sort(can.begin(),can.end());
        int k=0;
        while(k<can.size()&&can[k]<=target)
            k++;
        if(k<=0)
            return vector<vector<int> >();
        vector<vector<bool> > dp(k,vector<bool>(target+1,false));
        for(int i=0;i<=target;i++)
            if(i%can[0]==0)
                dp[0][i]=true;
        
        for(int i=1;i<k;i++)
        {
            for(int j=0;j<can[i];j++)
                dp[i][j]=dp[i-1][j];
            for(int j=can[i];j<=target;j++)
                dp[i][j]=dp[i-1][j]||dp[i][j-can[i]];
        }
        vector<vector<int> > ans;
        vector<int> path;
        createPath(ans,path,k-1,target,dp,can);
        return ans;
    }
    void createPath(vector<vector<int> >& ans,vector<int>& path,int k,int tar,vector<vector<bool> >& dp,vector<int>& can)
    {
        if(tar==0)
        {
            reverse(path.begin(),path.end());
            ans.push_back(path);
            reverse(path.begin(),path.end());
            return ;
        }
        if(k<0 || !dp[k][tar])
            return ;
        if(k>0&&dp[k-1][tar])
            createPath(ans,path,k-1,tar,dp,can);
        if (tar>=can[k]&&dp[k][tar-can[k]])
        {
            path.push_back(can[k]);
            createPath(ans,path,k,tar-can[k],dp,can);
            path.pop_back();
        }
    }
};






Combination Sum II:

Given a collection of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.

Each number in C may only be used once in the combination.

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 10,1,2,7,6,1,5 and target 8
A solution set is: 
[1, 7] 
[1, 2, 5] 
[2, 6] 
[1, 1, 6] 

这次是每个数只能用一次了,但是难度也没有增加,还是搜索跟DP都做一次,先来搜索的。

中间有个问题,比如 原数组是[1,1,1,1,1,],最后目标值是1,那么求出的ans会有5个{1},明显是不对的,我的办法是求出ans之后排序然后取出重复,这样的效率明显是不好的,有没有办法在求的过程中就不要生成这样重复的答案呢,想一想。

#define vi vector<int>
#define vvi vector<vi >
#define pb push_back
class Solution {
public:
    vector<vector<int> > combinationSum2(vector<int> &num, int target) {
        // Start typing your C/C++ solution below
        // DO NOT write int main() function
        
        int n=num.size();
	    sort(num.begin(),num.end());
	    int m=n;
	    for(int i=0;i<n;i++)
	    {
		    if ( num[i] > target )
		    {
			    m=i;
			    break;
		    }
	    }
	    vvi ans;
	    vi path;
	    solve(ans,path,num,0,m,0,target);
        sort(ans.begin(),ans.end());
        ans.erase(unique(ans.begin(),ans.end()),ans.end());
	    return ans;
    }
    void solve(vvi& ans,vi& path,vi& num,int k,int m,int sum,int target)
    {
	    if ( k >=m || sum>target || sum+num[k]>target )
		    return;

	    solve(ans,path,num,k+1,m,sum,target);
	    path.pb(num[k]);
	    sum+=num[k];
	    if ( sum==target ) 
		    ans.pb(path);
	    solve(ans,path,num,k+1,m,sum,target);
	    path.pop_back();
    }
};

昨天说到怎么可以在搜索的时候就避免重复结果的出现,其实比较简单啦。

比如对  { 5,5,5} ,target=5 来说,为了不产生3次{ 5} 。首先我们看这3个{5}是怎么产生的,如果我们用0和1表示该位置的数取和不取,那3个{5}对应的是{1,0,0}、{0,1,0}和{0,0,1}三种。在这些情况里面1和0是不连续的,如果我们可以让取和不取是连续的,即总是{1,1,1,0,0。。。}这种情况,那最后肯定是没有重复答案啦。

所以我们的剪枝策略是:

当当前元素跟前一个元素是相同的时候,如果前一个元素被取了,那当前元素可以被取,也可以不取,反过来如果前一个元素没有取,那我们这个以及之后的所以相同元素都不能被取。

#define vi vector<int>
#define vvi vector<vi >
#define pb push_back
class Solution {
public:
    vector<vector<int> > combinationSum2(vector<int> &num, int target) {
        // Start typing your C/C++ solution below
        // DO NOT write int main() function
        
        int n=num.size();
        sort(num.begin(),num.end());
	    int m=n;
	    for(int i=0;i<n;i++)
	    {
		    if ( num[i] > target )
		    {
			    m=i;
			    break;
		    }
	    }
	    vvi ans;
	    vi path;
	    solve(ans,path,num,0,m,0,target,0);
        //sort(ans.begin(),ans.end());
        //ans.erase(unique(ans.begin(),ans.end()),ans.end());
	    return ans;
    }
    void solve(vvi& ans,vi& path,vi& num,int k,int m,int sum,int target,int pre)
    {
	    if ( k >=m || sum>target || sum+num[k]>target )
		    return;

	    solve(ans,path,num,k+1,m,sum,target,0);

	    if ( k==0 || num[k]!=num[k-1] || pre==1  )
	    {
		    path.pb(num[k]);
		    sum+=num[k];
		    if ( sum==target ) 
			    ans.pb(path);
		    solve(ans,path,num,k+1,m,sum,target,1);
		    path.pop_back();
	    }
    }
};




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值