昨天详解了一下背包问题,以后有人问我若是每种元素均可以选择任意数目那会怎么样?这是很常见的背包问题的变种问题,只须要咱们在原来的算法基础上作一点小小的改动,咱们一块儿来看下。java
照例来看下题目定义:给定N种水果的重量跟收益,咱们须要把它们放进一个可容重量为C的背包里,使得包里的水果在总重量不超过C的同时拥有最高的收益,假设水果数量无限,一种可选多个
。c++
此次咱们还要去卖水果,在携带量有限的状况下得到最大的收益。假设档状况是: 水果: { 苹果, 橙子, 西瓜 }
重量: { 1, 2, 3 }
收益: { 15, 20, 50 }
可容重量: 5
。算法
咱们也一样先来稍稍列举下可能的状况: 5 苹果 (总重 5) => 75 收益
1 苹果 + 2 橙子 (总重 5) => 55 收益
2 苹果 + 1 西瓜 (总重 5) => 80 收益
1 橙子 + 1 西瓜 (总重5) => 70 收益
。数组
咱们能够看到两个苹果跟西瓜是绝配,载重量有限的状况下咱们得到了最大收益。关键是咱们得把这个过程经过代码表达出来,咱们来分析一下,对于每种水果,咱们能够选择放进去而后进行下一轮选择,或者不放进去直接进行下一轮选择,在每次放进去一种水果A以后,咱们还要选择要不要把A再放进去,知道超出背包的载重量,而后在这个过程当中咱们要选出两种选择中带来最大收益的那个。缓存
也照旧,咱们先用递归来把算法实现出来,后期再慢慢优化。上面已经描述得很清楚了,咱们能够直接写出来:post
private int knapsackRecursive(int[] profits, int[] weights, int capacity, int currentIndex) {
if (capacity <= 0 || profits.length == 0 || weights.length != profits.length ||
currentIndex >= profits.length)
return 0;
// 选择了当前元素以后继续循环处理,要注意这里选择结束后并无把索引+1
int profit1 = 0;
if (weights[currentIndex] <= capacity)
profit1 = profits[currentIndex]
+ knapsackRecursive(profits, weights, capacity - weights[currentIndex], currentIndex);
// 跳过当前元素而后继续作选择
int profit2 = knapsackRecursive(profits, weights, capacity, currentIndex + 1);
return Math.max(profit1, profit2);
}
复制代码
想必你们都看的出来,咱们的算法跟昨天的很类似,除了一些条件的变化。要注意的是这里的时间复杂度变成了O(2^(N+C)),N是元素元素数量,C是背包最大载重,由于咱们能够重复选择某一元素。优化
如今遇到这种问题,写出了暴力递归的作法,你们确定都能条件反射般地用缓存来优化算法了。这边已经不须要我卖关子了,我们直接上代码:spa
private int knapsackRecursive(Integer[][] dp, int[] profits, int[] weights, int capacity, int currentIndex) {
if (capacity <= 0 || profits.length == 0 || weights.length != profits.length ||
currentIndex >= profits.length)
return 0;
// 检查咱们以前有木有遇到过一样的子问题,有就直接返回结果
if (dp[currentIndex][capacity] == null) {
// 作完选择以后继续递归处理,注意选择后咱们还能够继续选择当前元素
int profit1 = 0;
if (weights[currentIndex] <= capacity)
profit1 = profits[currentIndex] + knapsackRecursive(dp, profits, weights,
capacity - weights[currentIndex], currentIndex);
// 跳过当前元素直接进行下一次递归
int profit2 = knapsackRecursive(dp, profits, weights, capacity, currentIndex + 1);
dp[currentIndex][capacity] = Math.max(profit1, profit2);
}
return dp[currentIndex][capacity];
}
复制代码
这时候由于咱们把子问题的结果都缓存在二维数组中,因此咱们最多进行了NC次计算,因此咱们的时间复杂度降低到了O(NC),可是如今想必你们也都能发现都发觉了一般光缓存是达不到最优的,那咱们再来试试从另外一个方向,采用自下而上的方式来思考这个问题。(又到了激动人心的环节了!)code
本质上,咱们仍是想在上面的递归过程当中,对于每个索引,每个剩余的可容重量,咱们都想在这一步得到能够的最大收益。咱们仍是面临两个选择,递归
dp[index-1][c]
。profit[index] + dp[index][c-weight[index]]
。最后咱们获得了想得到最大收益的公式:dp[index][c] = max (dp[index-1][c], profit[index] + dp[index][c-weight[index]])
。跟咱们昨天的思路简直一毛同样!
刚看完昨天文章的你们确定明白是怎么回事了,我也很少说了,直接把代码贴出来供你们观赏:
public int solveKnapsack(int[] profits, int[] weights, int capacity) {
if (capacity <= 0 || profits.length == 0 || weights.length != profits.length)
return 0;
int n = profits.length;
int[][] dp = new int[n][capacity + 1];
// 0载重量0收益
for (int i = 0; i < n; i++)
dp[i][0] = 0;
// 循环处理全部元素全部重量
for (int i = 0; i < n; i++) {
for (int c = 1; c <= capacity; c++) {
int profit1 = 0, profit2 = 0;
if (weights[i] <= c)
profit1 = profits[i] + dp[i][c - weights[i]];
if (i > 0)
profit2 = dp[i - 1][c];
dp[i][c] = profit1 > profit2 ? profit1 : profit2;
}
}
// 最大收益确定出如今最右下角
return dp[n - 1][capacity];
}
复制代码
发现没有,这个问题对咱们根本毫无压力?掌握了昨天的进阶文章,咱们甚至还能够对这个算法再进行优化两百遍!(其实两遍)
皮这一下真开心,最后的优化我就不带你们一块儿走了,思路都是同样的,留给你们去思考,你们平时作算法题的时候必定要多思考,尽力把题目转化成咱们熟悉的题目,转换成功后那咱们结题呀优化呀一切都游刃有余了。