题目叙述
给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。
示例:
nums = [1, 2, 3]
target = 4
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。因此输出为 7。
分析
方法1 简单递归
直接使用进行递归求解。但是代码会超时。
class Solution {
public:
int res = 0;
int combinationSum4(vector<int>& nums, int target) {
//使用深度搜索
dfs(nums, target, 0);
return res;
}
void dfs(vector<int>& nums, int target, int sum){
if(sum>target) return;
if(sum == target){
res++;
return;
}
for(int i=0;i<nums.size();i++){
dfs(nums, target, sum+nums[i]);
}
}
};
也可以如下图,自顶向下的递归。但是也是会超时。
class Solution {
public:
int res = 0;
//记忆化递归,使用一个数组来存储当target为i时所有的结果
vector<int> dp;
int combinationSum4(vector<int>& nums, int target) {
//使用深度搜索
dp.resize(target+1, 0);
dfs(nums, target);
return res;
}
void dfs(vector<int>& nums, int target){
if(target<0) return;
if(!target){
res++;
return;
}
for(int i=0;i<nums.size();i++){
dfs(nums, target-nums[i]);
}
}
};
方法2 记忆化递归
观察上图,由于存在很多重复的子问题,因此考虑使用记忆化递归。
class Solution {
public:
//记忆化递归,使用一个数组来存储当target为i时所有的结果
vector<unsigned long long> dp;
int combinationSum4(vector<int>& nums, int target) {
//先排个序
sort(nums.begin(), nums.end());
//使用深度搜索
dp.resize(target+1, -1);//注意数组的初始化要初始化一个不可能的数,此处只能取负数,注意不能取0,因为dp[i]实际有可能取0,因此只能用负数表示还没有求解
dfs(nums, target);
return dp[target];
}
int dfs(vector<int>& nums, int target){
if(!target){
return 1;
}
if(dp[target]!=-1){//对于记忆化递归,当直接返回的时候,建议采用不等号,初始化为-1即一个不可能的数字,那么就表示没有被使用
return dp[target];
}
int ans = 0;
for(int i=0;i<nums.size();i++){
if(target<nums[i]) break;
ans += dfs(nums, target-nums[i]);
if(ans>0) dp[target] += ans;
}
dp[target] = ans;
return dp[target];
}
};
方法3 使用动态规划求解
常见的背包问题有1、组合问题。2、True、False问题。3、最大最小问题。具体可以参考。
在本题还应该注意的是,零钱兑换和求组合的情况总和很类似,但是两者不同的是零钱兑换需要的获取凑成指定target的所有的货币的组合的情况数目,这个组合的情况是没有排序的,即[1, 2, 3] 和[1, 3, 2]其实是一样的。但是针对该题组合的总和是排序的,即[1, 2, 3]和[1, 3, 2]是一样的。两者只需要进行交换内外循环的顺序。
对于兑换硬币:
for(int num:nums){
for(int j =1;j<=target;j++)
dp[j] += dp[j-num];
}
对于本题的组合问题
for(int j =1;j<=target;j++){
for(int num:nums)
dp[j] += dp[j-num];
}
仔细分析就会想到,第一种方式隐含了在取nums中的num元素的时候,位置就已经定死了,比如dp[6]可以由[1,2,3]构成,不能由[3, 2,1]构成,而这种限制在第二种内外循环下就不存在。
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
vector<unsigned long long> dp(target+1, 0);//此处如果不使用类型unsigned long long则会报溢出的错误
dp[0] = 1;//注意这个一定要初始化为1而不是0
for(int i=1;i<=target;i++){
for(int j=0;j<nums.size();j++){//待选的不重复的数字
if(i>=nums[j]) dp[i] += dp[i-nums[j]];//当i==nums[j]时,以nums[j]为结尾的所有排列就一个,所以可知直接使dp[0]=1实现这一目的
}
}
return dp[target];
}
};
或者对于等于0的情况,单独讨论:
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
vector<unsigned long long> dp(target+1, 0);//此处如果不使用类型unsigned long long则会报溢出的错误
dp[0] = 0;//注意这个一定要初始化为1而不是0
for(int i=1;i<=target;i++){
for(int j=0;j<nums.size();j++){//待选的不重复的数字
if(i>nums[j]) dp[i] += dp[i-nums[j]];//当i==nums[j]时,以nums[j]为结尾的所有排列就一个,所以可知直接使dp[0]=1实现这一目的
if(i==nums[j]) dp[i] += 1;
}
}
return dp[target];
}
};