目录
题目:
牛牛准备参加学校组织的春游, 出发前牛牛准备往背包里装入一些零食, 牛牛的背包容量为w。
牛牛家里一共有n袋零食, 第i袋零食体积为v[i]。
牛牛想知道在总体积不超过背包容量的情况下,他一共有多少种零食放法(总体积为0也算一种放法)。
输入描述:
输入包括两行 第一行为两个正整数n和w(1 <= n <= 30, 1 <= w <= 2 * 10^9),表示零食的数量和背包的容量。 第二行n个正整数v[i](0 <= v[i] <= 10^9),表示每袋零食的体积。
输出描述:
输出一个正整数, 表示牛牛一共有多少种零食放法。
示例1
输入
3 10 1 2 4
输出
8
说明
三种零食总体积小于10,于是每种零食有放入和不放入两种情况,一共有2*2*2 = 8种情况。
思路分析:
暴力递归:
- 定义一个fun(int i,int rest)方法。
- 用从左到右尝试模型:fun(i, reat)返回在从左到右放到第i个物品时,有多少种可能性
- 当rest
- 当i越界时(i>arr.length),说明所有的物品已经放下,返回1
- 否则,返回i放进背包,或者不放进背包的可能性
- 如果i放进背包,则调用fun(i+1, rest-arr[i])
- 如果i不放进背包,则调用fun)i+1, rest)
- 将以上两种可能性相加作为返回值。
动态规划1:
- 在暴力递归中,一个函数都有两个参数,我们可以用这两个参数作为数组的索引
- 定义一个dp[][] = new int[i][rest]二维数组,dp[i][rset]表示从i到最后一个物品,背包剩余rest有多少种可能性
- 初始化:
- 首列:背包容量为0,说明么都不用放,可能性始终为1
- 末行:背包容量最后一个物品体积时,放或不放,可能性为2。
- 其余dp[i][j]:
- 当剩余容量 < 小于当前物品体积时,不放入背包,可能性为dp[i+1][j]。
- 当剩余容量 > 小于当前物品体积时,放入或者不放入,可能性相加:dp[i+1][j] + dp[i+1][ j-arr[i] ];
- 结果:
- dp[0][w]即为可能性结果。
动态规划2:
- 定义二维数组dp[][] = new int[n][w+1]
- dp[i][j]的含义是:第i个物品到第n-1个物品,抽出若干物品恰好组成总体积j的可能性。
- 初始化:
- 首列:背包容量为0,只有一种可能性,就是什么都不放入背包
- 末行:除了背包容量为0时。背包容量=最后一个物品体积时,可能性为1;否则为0
- 其余dp[i][j]:
- 当剩余容量 < 小于当前物品体积时,不放入背包,可能性为dp[i+1][j]。
-
- 当剩余容量 > 小于当前物品体积时,放入或者不放入,可能性相加:dp[i+1][j] + dp[i+1][ j-arr[i] ];
- 结果:
- 第一行的元素和,即为结果。
样例推到:
暴力递归:
动态规划1:
动态规划2:
代码展示:
暴力递归:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
public class Main{
private static int[] array;
private static int info(int i,int rest){
if(rest<0){
return 0;
}
if(i == array.length){
return 1;
}
return info(i+1,rest) + info(i+1,rest-array[i]);
}
public static void main(String[] args)throws IOException{
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String[] str_nw = reader.readLine().split(" ");
String[] str = reader.readLine().split(" ");
int n = Integer.parseInt(str_nw[0]);
int w = Integer.parseInt(str_nw[1]);
array = new int[n];
for(int i=0;i<n;i++){
array[i] = Integer.parseInt(str[i]);
}
System.out.print(info(0, w));
}
}
有两组n比较大的数据不能通过
优化:
可以看到,物品体积小,物品个数很多时,仍然会调用递归函数,调用的次数将会是O(2^N)。
此时其实可以直接得出答案,背包容量足够大,每一个物品都可以放或者不放,答案为n个2相乘,即1所以,在for循环中输入物品时,先在物品总和求出来,如果物品的总和小于背包容量直接返回1
动态规划1:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
public class Main{
private static int n;//物品数
private static int w;//背包总容量
private static int[] array;//物品体积数组
private static int[][] dp;//动态规划二维数组
private static void setDp(){
//初始化
for(int i=0;i<n;i++){
dp[i][0] = 0;
}
for(int j=0;j<= w;j++){
dp[n-1][j] = (j<array[n-1])?1:2;
}
for(int i = n-2;i>=0;i--){
for(int j = 1;j<=w;j++){
dp[i][j] = dp[i+1][j] + ((array[i]>j)?0:dp[i+1][j-array[i]]);
}
}
}
public static void main(String[] args)throws IOException{
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String[] str_nw = reader.readLine().split(" ");
String[] str = reader.readLine().split(" ");
n = Integer.parseInt(str_nw[0]);
w = Integer.parseInt(str_nw[1]);
array = new int[n];
dp = new int [n][w+1];
for(int i=0;i<n;i++){
array[i] = Integer.parseInt(str[i]);
}
setDp();
System.out.print(dp[0][w]);
}
}
动态规划是用空间换时间,当背包容量巨大时,申请一个这么大的二维数组汇报异常OOM,
这个题目用动态规划确实不好做。
动态规划2:
动态规划1会报OOM异常,那么动态规划2也会,所以就不再敲一遍了