先看这个图,就明白很多了,相当于计算叶子节点数量。
其实这题更容易想到dfs回溯,但是重复子问题太多的话,可能会超时,所以最好使用dp。
参考:希望用一种规律搞定背包问题 - 组合总和 Ⅳ - 力扣(LeetCode)
顺序相关,实际在求排列数。
这是一个完全背包问题:
物品(nums的元素)选取不限制,物品就是数组元素,背包容量就是target,求的是刚好装满背包的方法数。完全背包问题的大体代码模板就是:
int dp[target+1];//dp[i]表示和值为 i 的时候的最大价值。
...
for()//枚举物品
for()//枚举容量
dp[j] = max(dp[j], dp[j-v]+w);
这题求的是排列数,所以把max处改为相加。
int dp[target+1];//dp[i]表示和值为 i 的时候可能的组合数。
...
for()//枚举物品
for()//枚举容量
dp[j] += dp[j-v];
但是这样其实还不行,这样实际上是在求组合数(顺序无关),而本题实际是在求排列数(顺序相关)。完全背包一维dp的时候,两次for循环实际上是可以颠倒的。但是在这儿,必须进行颠倒,也就是必须先枚举容量,再枚举物品,原因是我们需要保证每次更新dp数组值的时候,所有的物品都是可选的,这样才能保证求的是排列数。
初始值dp[0] = 1, 也就是和值为0的选取方法只有一种(那就是不选),需要的是dp[target]:
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
if(nums.size() == 0) return 0;
vector<unsigned long long> dp(target+1, 0);//dp[i]表示和值为 i 的时候可能的组合数。
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 (分组背包,互斥);
或者限制负数的使用次数,设计成多重背包问题的样子。