给你一个由 不同 整数组成的数组 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 <= 2001 <= nums[i] <= 1000nums中的所有元素 互不相同1 <= target <= 1000
解题思路
看到这种组合类型题,我们会下意识地去想到背包问题,特别是当一个元素还能被多次使用的时候,会误以为就是完全背包问题。
但是我们必须知道一点,背包问题针对的是组合问题,但是这道题实际上是排列问题,所以背包问题是解决不了这种题的!但是大多数的题解中解释的很牵强,使劲的往背包问题上靠拢,虽说最后的代码确实和背包问题很相似,也能跑通代码,但其实这已经不是背包问题的思想了!
其实这道题就是简单的动态规划问题,只不过我们会因为不了解题目已经背包问题的解决类型,而导致深陷泥潭无法自拔无法理解!所以说这道题的题目其实也不严谨,更严谨的题目应该是排列总和才对!
为什么说背包问题解决的是组合问题呢❓❓❓
就比如我们之前设定的背包问题的状态:dp[i][j] 表示在前 i 个物品中选择,总体积不超过 j,此时的所有选法中的最大价值。
但是仔细一分析,确实是完成不了排列问题,因为每次走到 i 位置的时候,其又需要和前面的元素关联起来,也就是说,每个元素的状态都和前后有关系,这样子的状态是不能推导出转移方程的!因为排列问题,顺序一变就是一个新组合!
所以我们就要用最朴素的动态规划解法来解决这道题!!
状态表示
现在我们是知道目标和 target 的,并且要求的就是该目标和的排列组合个数,那我们就创建一个一维的 dp 表,下标就表示当前的目标和,其元素大小就是排列组合的个数。
简单地说,状态定义为 dp[i] 表示当前总和为 i,此时的排列组合的个数。
状态转移方程
假设当前元素下标为 j,其元素大小为 nums[j],并且 0 <= j < n,因为我们是推导 dp[i],而此时 i 就表示当前的目标和,那么我们此时 i 和 nums[j] 的关系如下图所示:
那么此时问题又转变成去求满足 i - nums[j] 这些元素:
并且有可能我们找到的 i - nums[j] 是小于 0 的,此时数组访问的时候就会越界,所以我们需要判断 i - nums[j] >= 0 的时候才去累加每个目标和的排列组合个数,即 dp[i - nums[j]]。
所以状态转移方程为:dp[i] += dp[i - nums[j]],其中 0 <= j < n,且 i - nums[j] >= 0。
初始化
因为我们要开辟虚拟行列,所以当 i 为 0 的时候,表示总和为 0 的时候排列的个数,而后面因为涉及到累加,为了后面填表的正确,这里将 dp[0] 初始化为 1 即可!
遍历顺序
因为用到前面的位置,所以要从左往右遍历!
返回值
根据状态表示,返回 dp[target] 即可!
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
// dp[i]表示当前总和为i,此时的排列组合的个数
vector<double> dp(target + 1); // 因为int会溢出,所以用double
// 初始化
dp[0] = 1;
// 从左往右遍历
int n = nums.size();
for(int i = 1; i <= target; ++i)
{
for(int j = 0; j < n; ++j)
{
if(i >= nums[j])
{
dp[i] += dp[i - nums[j]];
}
}
}
return dp[target];
}
};