本文参考这两篇题解:
https://programmercarl.com/0518.%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2II.html#思路,以及https://programmercarl.com/0377.%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C%E2%85%A3.html#思路
题目描述:
给你一个由 不同 整数组成的数组 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)
请注意,顺序不同的序列被视作不同的组合。
方法一:
class Solution {
public:
int combinationSum4(vector& nums, int target) {
vector dp(target + 1, 0);
dp[0] = 1;
for (int j = 0; j <= target; j++)
{
for (int i = 0; i < nums.size(); i++)
{
if(j >= nums[i] && dp[j] < INT_MAX - dp[j - nums[i]])
{
dp[j] = dp[j] + dp[j - nums[i]];
}
}
}
return dp[target];
}
};
这一道题乍一看和【零钱兑换 II】一题很像,于是我也就直接套用了原来那题的代码,但是发现输出却根本不一样——原因在于【零钱兑换 II】一题所要求寻找的是有多少种组合方式,例如{1,2,3}和{2,1,3}实际上算是同一种组合;而这题所要求寻找的是有多少种排列方式,例如{1,2,3}和{2,1,3}算是两种不同的排列。
首先dp[j]的含义为:凑成整数j有多少种排列方式,容易推出递推关系式:
dp[j] = dp[j] + dp[j - nums[i]];
关于遍历顺序:
查看了题解后,我发现关于组合和排列的区别就在于物品和背包的先后遍历顺序上——
①先物品后背包,所找到的是组合方式,因为例如对于物品0和物品1,这种遍历顺序就是先考虑将物品0放入背包,再考虑将物品1放入背包,只能得到{0,1}的排列而无法得到{1,0}的排列。
②先背包后物品,由于对于每个背包容量,都充分考虑了各个物品放入包内的情况,因此可以得到{0,1}和{1,0}两种排列方式。对于这题而言,由递推关系式可知:
最后一个关于dp[4]的式子可以这样理解,dp[4]可以由三部分组成:dp[3]和1,dp[2]和2,dp[1]和3。下图或许更好理解:
可以看出,在dp[4]的计算过程中,就考虑到了各种可能的排列方式。
总结:
这道【组合总和 Ⅳ】与【零钱兑换 II】的区别就在于物品和背包的遍历顺序上,这两道题弄懂之后对于完全背包问题的遍历顺序就能够有更进一步的理解。