
题解:
dpi 目标位i的组合组数是多少
dpi = dpi-A0 + dpi-A\[1] .. dp1-A\[n-1],枚举所有最后一个数字的可能








cpp
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
int len = nums.size();
if (len <= 0) {
return 0;
}
vector<unsigned long long> dp(target + 1, 0);
dp[0] = 1;
for (int i = 0; i <= target; ++i) {
for (int j = 0; j < nums.size(); ++ij) {
if (i - nums[j] >= 0) {
dp[i] += dp[i - nums[j]];
}
}
}
return dp[target];
}
};
外层循环决定"转移时被复用的 dp 值是完整的,还是只含部分物品的"。
本题 dp[j] 的语义是"和为 j 的排列数",转移是:
cpp
dp[j] = Σ_{num ∈ nums, num ≤ j} dp[j - num]
要让这个式子成立,右边每个 dp[j - num] 必须是完整的排列数(所有物品都能参与)。而它什么时候完整,取决于循环顺序。
1. 从"最后一步"看
一个和为 j 的排列,最后一步 可以是任意 num,前面部分是任意和为 j - num 的排列。
所以:
text
dp[j] = dp[j-1] + dp[j-2] + dp[j-3] + ...
↑ 最后一步是 1、2、3……
这里 dp[j-num] 必须是"任何物品都能用"的完整排列。
外层容量 j:
cpp
for (int j = 1; j <= target; ++j)
for (int num : nums)
dp[j] += dp[j - num];
当你在第 j 层累加时,dp[j-num] 已经在之前的轮次里把所有物品都累加完了,它是完整的。✓
外层物品 num:
cpp
for (int num : nums)
for (int j = num; j <= target; ++j)
dp[j] += dp[j - num];
当你在处理物品 num 时,dp[j-num] 只吸收了当前及之前处理过的物品,还没轮到后面的物品。它是"半成品"。用半成品去转移,就等于强制"前面的物品编号 ≤ 当前物品编号"