问题
给你一个可放总重量为 W 的背包和 N 个物品,对每个物品,有重量 w 和价值 v 两个属性,那么第 i 个物品的重量为 w[i],价值为 v[i]。现在让你用这个背包装物品,每种物品都可以选择任意多个,问这个背包最多能装的价值是多少?
示例:输入:W = 5, N = 3w = [3, 2, 1], v = [5, 2, 3]输出:15解释:当 i = 2 时,选取 5 次,总价值为 5 * 3 = 15。
算法问题分析
不同于 0-1 背包问题(每件物品只能拿一次),在完全背包问题中,每件物品可以拿任意多件,只要背包装得下就行。
如果从每件物品的角度来看,与之相关的决策已经不再是选拿(1)或者不拿(0)了;而是拿 0 件、拿 1 件、拿 2 件……直到拿到 (W/w[i]) 件物品为止。
状态转移方程

代码实现java版
public static void main(String[] args) {int N = 3, W = 5; // 物品的总数,背包能容纳的总重量int[] w = {0, 3, 2, 1}; // 物品的重量int[] v = {0, 5, 2, 3}; // 物品的价值int dp = dp(w, v, N, W);System.out.println(dp);}/*** @param w 重量数组* @param v 价值数组* @param N 有多少个可选物品* @param W 背包容量*/public static int dp(int[] w, int[] v, int N, int W) {// 创建备忘录int[][] dp = new int[N + 1][W + 1];//for (int tn = 1; tn < N + 1; tn++) {for (int rw = 1; rw < W + 1; rw++) {//if (rw < w[tn]) {// 不选dp[tn][rw] = dp[tn - 1][rw];} else {int maxNum = rw / w[tn];for (int k = 0; k <= maxNum; k++) {// 前n-1种物品的剩余可装量int otherW = rw - k * w[tn];dp[tn][rw] = Math.max(dp[tn][rw], dp[tn - 1][otherW] + k * v[tn]);}}}}return dp[N][W];}
输出
15
状态转移过程
时间复杂度优化
如果我们认真分析上面的代码,就可以发现代码中使用了三重循环:
- 首先是遍历物品;
- 然后是遍历剩余容量;
- 最后是遍历物品数量。
那么这个解法的算法时间复杂度是多少呢?如果我们假定物品数量是 k,容量是 v,那么最后的时间复杂度就是 O(kv²)。
改进代码的时间复杂度
我们查看上一份代码不难看出,对于第tn个物品,其rw=0,1,2,3….W,前每个列都要遍历多遍,这属于重复子问题,我们就可以将其转为0-1背包问题,不管前面已经使用了tn这个物品几次,只关心当前容量这一次是否需要放进去即可。
/*** @param w 重量数组* @param v 价值数组* @param N 有多少个可选物品* @param W 背包容量*/public static int dp(int[] w, int[] v, int N, int W) {// 创建备忘录int[][] dp = new int[N + 1][W + 1];//for (int tn = 1; tn < N + 1; tn++) {for (int rw = 1; rw < W + 1; rw++) {//if (rw < w[tn]) {// 不选dp[tn][rw] = dp[tn - 1][rw];} else {dp[tn][rw] = Math.max(dp[tn][rw], dp[tn][rw - w[tn]] + v[tn]);}}}return dp[N][W];}
其得到的状态转移过程一致
