377. 组合总和 Ⅳ - 力扣(LeetCode)
先看这个图,就明白很多了,相当于计算叶子节点数量。
求个数,而不是具体的解,所以一般使用动态规划。
参考:希望用一种规律搞定背包问题 - 组合总和 Ⅳ - 力扣(LeetCode)
有点类似:LeetCode第 322 题:零钱兑换(C++)_zj-CSDN博客这儿的目标值相当于找零的钱,数组元素相当于硬币。
考虑顺序,实际上在求排列数。
可以看到这是一个背包问题:
背包问题具备的特征:给定一个target,target可以是数字也可以是字符串,再给定一个数组nums,nums中装的可能是数字,也可能是字符串,问:能否使用nums中的元素做各种排列组合得到target。
如果dp的话,怎么定义状态呢?首先思考是需要一维的dp还是二维的dp?
背包问题一般都是一维的dp(如果不熟练,还是建议先写二维的,然后再状态压缩),用dp[i]表示和值为 i 的时候可能的组合数。
假设现在知道了dp[i],也就是组成和值 i 的方法有dp[i]种,那么对于数组中的某个元素num,我们现在至少可以推导出组成和值为 i+num的方法数有dp[i]种(把每一种组成和值 i 的组合统统加一个num即可),这么说可能不太明确,反过来理解:
假设我们现在要计算dp[i],先思考什么样的状态有可能转移到dp[i]呢?从dp[0]到dp[i-1]的状态,都有可能转移到dp[i],只要数组中刚好存在这个差值即可。所以我们需要枚举数组nums中的值(类似零钱找零那个题,需要枚举硬币)。由于数组元素都是正数,所以枚举很容易:如果数组元素nums[j] <= i ,那就说明状态 i 可以从状态 i - nums[j]转移过来:dp[i] += dp[i - nums[j]];
dp[i] = sum{dp[i - num] for num in nums and if i >= num}
初始值可以设定dp[0] = 1, 也就是和值为0的选取方法只有一种(那就是不选),我们需要其实就是dp[target]:
class Solution {
public:
int combinationSum4(vector& nums, int target) {
if(nums.size() == 0) return 0;
vector dp(target+1, 0);
dp[0] = 1;
for(int i = 0; i <= target; ++i){
for(const auto &num : nums){
if(num <= i) dp[i] += dp[i - num];
}
}
return dp[target];
}
};
进阶:
参考:动态规划 - 组合总和 Ⅳ - 力扣(LeetCode)
如果给定的数组中含有负数会怎么样?
如果有负数,相当于给定数组中的元素有了更多的组合,特别是出现了一对相反数的时候,例如题目中的示例 [-4, 1, 2, 3, 4],target = 4 的时候,-4 和 4 可以无限次地、成对添加到题目中的示例中,成为新的组合,那么这道问题就没有什么意义了。
问题会产生什么变化?
我们需要在题目中添加什么限制来允许负数的出现?
如果有负数参与进来,不能够与已有的正数的组合之和为 0 ;
或者限制负数的使用次数,设计成类似 0-1 背包问题的样子。