01背包
问题描述:有N件商品,第i件商品的重量是weights[i-1],价值是values[i-1],背包容量是cap
则背包能够装物品的最大价值
首先构建一个二维数组dp,dp[i][j]表示第i件物品时背包容量为j时的最大价值。
如何求得dp[i][j]:
(1)values[i-1]>j,说明第i件物品不能放入当前容量为j的背包,即不放入第i件商品:
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
dp[i][j]=dp[i-1][j]
dp[i][j]=dp[i−1][j]
(2)values[i-1]<=j,则第i件物品可以放入到容量为j时的背包中,但是这时候需要考虑放进第i件物品的背包中物品价值是否比不放时大:
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
−
1
]
[
j
−
w
e
i
g
h
t
s
[
i
−
1
]
]
+
v
a
l
u
e
s
[
i
−
1
]
)
dp[i][j] = max(dp[i-1][j], dp[i-1][j-weights[i-1]]+values[i-1])
dp[i][j]=max(dp[i−1][j],dp[i−1][j−weights[i−1]]+values[i−1])
max中的第一项表示不放,第二项表示放入第i件,放入第i件就需要把背包腾出至少weights[i-1]的容量,举个例子:weights = {3, 4, 3, 2, 5};values = {2, 4, 4, 3, 2};当i=2,j=4时,此时需要放入背包的物品重量为4,价值为4,而dp[i-1][j] = 2,所以需要将第一件重量为3价值为2的商品拿出,放入第2件商品,即dp[2][4] = dp[1][0]+4 = 4。
/*
* 01背包:背包可装的最大价值
* @param 商品重量
* @param 商品价值
* @param 背包容量
*/
public int maxValue(int[] weights, int[] values, int cap) {
if(cap == 0 || weights.length == 0) {
return 0;
}
if(weights.length != values.length) {
System.out.println("数组长度不一致");
return -1;
}
int num = weights.length;
int[][] dp = new int[num+1][cap+1];
for(int i = 1; i <= num; i++) {
for(int j = 1; j <= cap; j++) {
if(j < weights[i-1]) {
dp[i][j] = dp[i-1][j];
}
else {
dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-weights[i-1]]+values[i-1]);
}
}
}
return dp[num][cap];
}
优化
进行优化:
不难看出dp[i][]只和dp[i-1][]有关,因此可以使用两个一维数组来代替二维数组进行动态规划的迭代,空间复杂度由O(N*cap)降低为O(cap)。
public int maxValue(int[] weights, int[] values, int cap) {
if(cap == 0 || weights.length == 0) {
return 0;
}
if(weights.length != values.length) {
System.out.println("数组长度不一致");
return -1;
}
int num = weights.length;
int[] dp1 = new int[cap+1];
int[] dp2 = new int[cap+1];
for(int i = 1; i <= num; i++) {
for(int j = 1; j <= cap; j++) {
if(j < weights[i-1]) {
dp2[j] = dp1[j];
}
else {
dp2[j] = Math.max(dp1[j], dp1[j-weights[i-1]]+values[i-1]);
}
}
dp1 = dp2.clone();
}
return dp2[cap];
}
实际上还可以进行优化,只使用一个一维数组即可,空间复杂度仍然是O(cap)。不难看出dp2[j]在更新时只和dp1[0…j-1]有关,而不会使用dp1[j…cap],因此可以逆序对dp2进行更新:
int[] dp2 = new int[cap+1];
for(int i = 1; i <= num; i++) {
for(int j = cap; j >= 1; j--) {
if(j < weights[i-1]) {
dp2[j] = dp2[j];
}
else {
dp2[j] = Math.max(dp2[j], dp2[j-weights[i-1]]+values[i-1]);
}
}
}
return dp2[cap];
要求恰好装满背包
上述描述不要求恰好装满背包,如果要求恰好装满背包的前提下,求背包所能装物品的最大价值,只需要修改初始化条件即可:j = 0时,dp为0,其他都为-∞。
int[] dp2 = new int[cap+1];
for(int i = 1; i <= cap; i++) {
dp2[i] = Integer.MIN_VALUE;
}
如果无法恰好装满背包,则会返回一个很大的负数。
原因:如果要求恰好装满,只有容量为0的时候什么也没装是恰好装满的,而其他容量初始时不符合要求,所以应该赋值为-∞,从下面的例子可以看出,没有恰好装满时,其价值是一个很大的负数。
背包容量为10;
int[] weights = {3, 4, 8, 8, 5};
int[] values = {2, 4, 4, 3, 2}; // 没有合法解
[0, -2147483648, -2147483648, 2, -2147483646, -2147483646, -2147483646, -2147483646, -2147483646, -2147483646, -2147483646]
[0, -2147483648, -2147483648, 2, 4, -2147483644, -2147483644, 6, -2147483642, -2147483642, -2147483642]
[0, -2147483648, -2147483648, 2, 4, -2147483644, -2147483644, 6, 4, -2147483642, -2147483642]
[0, -2147483648, -2147483648, 2, 4, -2147483644, -2147483644, 6, 4, -2147483642, -2147483642]
[0, -2147483648, -2147483648, 2, 4, 2, -2147483644, 6, 4, 6, -2147483642]
int[] weights = {3, 4, 3, 2, 5};
int[] values = {2, 4, 4, 3, 2}; // 有解,为10
[0, -2147483648, -2147483648, 2, -2147483646, -2147483646, -2147483646, -2147483646, -2147483646, -2147483646, -2147483646]
[0, -2147483648, -2147483648, 2, 4, -2147483644, -2147483644, 6, -2147483642, -2147483642, -2147483642]
[0, -2147483648, -2147483648, 4, 4, -2147483644, 6, 8, -2147483640, -2147483640, 10]
[0, -2147483648, 3, 4, 4, 7, 7, 8, 9, 11, 10]
[0, -2147483648, 3, 4, 4, 7, 7, 8, 9, 11, 10]