LeetCode第 377 题:组合总数 IV(C++)

377. 组合总和 Ⅳ - 力扣(LeetCode)

LeetCode第 377 题:组合总数 IV(C++)_第1张图片

先看这个图,就明白很多了,相当于计算叶子节点数量。

求个数,而不是具体的解,所以一般使用动态规划。

参考:希望用一种规律搞定背包问题 - 组合总和 Ⅳ - 力扣(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 背包问题的样子。

你可能感兴趣的:(leetcode,leetcode,动态规划)