专题十一:似包⾮包

83 阅读2分钟

1 组合总和 Ⅳ

1.1 题目链接

377. 组合总和 Ⅳ

1.2 题目描述

给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。

题目数据保证答案符合 32 位整数范围。

 

示例 1:

输入: nums = [1,2,3], target = 4
输出: 7
解释:
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。

示例 2:

输入: nums = [9], target = 3
输出: 0

 

提示:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 1000
  • nums 中的所有元素 互不相同
  • 1 <= target <= 1000

1.3 解法(动态规划)

算法思路

⼀定要注意,我们的背包问题本质上求的是「组合」数问题,⽽这⼀道题求的是「排列数」问题。

因此我们不能被这道题给迷惑,还是⽤常规的 dp 思想来解决这道题。

  1. 状态表⽰:

    • 这道题的状态表⽰就是根据「拆分出相同⼦问题」的⽅式,抽象出来⼀个状态表⽰:
    • 当我们在求 target 这个数⼀共有⼏种排列⽅式的时候,对于最后⼀个位置,如果我们拿出数组中的⼀个数 x ,接下来就是去找 target - x ⼀共有多少种排列⽅式。
    • 因此我们可以抽象出来⼀个状态表⽰:
      • dp[i] 表⽰:总和为 i 的时候,⼀共有多少种排列⽅案。
  2. 状态转移⽅程:

    • 对于 dp[i] ,我们根据「最后⼀个位置」划分,我们可以选择数组中的任意⼀个数nums[j] ,其中 0 <= j <= n - 1 。
    • 当 nums[j] <= target 的时候,此时的排列数等于我们先找到 target - nums[j] 的⽅案数,然后在每⼀个⽅案后⾯加上⼀个数字 nums[j] 即可。
    • 因为有很多个 j 符合情况,因此我们的状态转移⽅程为:
      • dp[i] += dp[target - nums[j] ,其中 0 <= j <= n - 1 。
  3. 初始化:

    当和为 0 的时候,我们可以什么都不选,「空集」⼀种⽅案,因此 dp[0] = 1 。

  4. 填表顺序:

    根据「状态转移⽅程」易得「从左往右」。

  5. 返回值:

    根据「状态表⽰」,我们要返回的是 dp[target] 的值。

1.4 C++算法代码:

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<double> dp(target + 1);
        dp[0] = 1;
        for(int i = 1; i <= target; i++)
            for(int e : nums)
                if(i >= e)
                    dp[i] += dp[i - e];
        return dp[target];
    }
};