int f[][] = new int[n + 1][V + 1]; // 前i个元素凑成的子集和为j最短长度。
f[0][0] = 0; // 不用选任何元素即满足,长度0
for (int i = 1; i <= n; i++) {
f[i][0] = Integer.MAX_VALUE;
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= V; j++) {
f[i][j] = f[i - 1][j]; // 默认不选物品i
if (j - nums[i] >= 0 && f[i - 1][j - nums[i]] != Integer.MAX_VALUE) {
// 与选择i时能达到的长度比较,选择了i意味着用前i-1个物品装满j - nums[i]
f[i][j] = Math.min(f[i][j], f[i - 1, j - nums[i]] + 1);
}
}
}
return f[n][V];
int f[] = new int[V + 1];
f[0] = 0;
for (int i = 1; i <= V; i++) {
f[i] = Integer.MAX_VALUE;
}
for (int i = 0; i < n; i++) {
// 这里要倒着来,因为在开始前f里相当于存着f[i-1][]
for (int j = V; j >= nums[i]; j--) {
if (f[j - nums[i]] != Integer.MAX_VALUE) {
f[j] = Math.min(f[j], f[j - nums[i]] + 1);
}
}
}
return f[V];
int f[] = new int[V + 1];
f[0] = 0;
for (int i = 1; i <= V; i++) {
f[i] = Integer.MAX_VALUE;
}
for (int i = 0; i < n; i++) {
// 这里要正着来,因为f[i][j] = min(f[i - 1][j], f[i][j - k * nums[i]] + 1 | k = 1,2,...,V/nums[i])
// 当转成一维,0-1背包倒着来是“为了保证每件物品只选择一次,在考虑i时依赖绝不包含i的子结果f[i-1,j-nums[i]]
// 而现在恰是每件物品可以选择无数次,正需要一个可能已选入i的子结果f[i][j-nums[i]]
// 因此f[i][j] = min(f[i-1][j],f[i][j-nums[i]] + 1),
// 其中f[i][j-nums[i]]又可以展开成与j - 2 * nums[i]相关的, 依次类推直到j - V/nums[i] * nums[i]
// 所以在j循环开始前f[j]初始值即f[i-1][j], 当j增加时相当于从j-V/nums[i]*nums[i]从底往前递推。
for (int j = nums[i]; j <= V; j++) {
if (f[j - nums[i]] != Integer.MAX_VALUE) {
f[j] = Math.min(f[j], f[j - nums[i]] + 1);
}
}
}
return f[V];
有些题看上去像背包问题,但实际上不方便套用。比如Combination Sum IV。因为背包问题的解本质上是combination而不是permutation。如果选的物品顺序也有关系则无法直接使用背包模板。
关于状态转移方程, 当求how many ways时f[j] += f[j - nums[i]], 当问求能刚好满足和为V的子集的最短长度f[j] = Math.min(f[j], f[j - nums[i]] + 1);