背包问题

看到从给定的一堆items按序选取几个(针对每个j <V, 每个item只选择一次)使他们的某个数量加起来的和最大(<V)或最小或刚好等于V,则想到0-1背包。

如果一件物品能选择无数次,则是完全背包,如322 Coin Change。

0-1背包的套路: 从nums[]中选,每个元素能选一次,求能刚好满足和为V的子集的最短长度。

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];

我们可以看到,f[i][]的状态只与f[i-1]有关,因此我们可以用一维数组优化空间。

完全背包: 从nums[]中选,每个元素能选无限次,求能刚好满足和为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);

Last updated

Was this helpful?