用java解决背包问题-可能性 (三种解法)

目录

题目:

输入描述:

输出描述:

示例1

输入

输出

说明

思路分析:

暴力递归:

动态规划1:

动态规划2:

样例推到:

暴力递归:

动态规划1:

动态规划2:

代码展示:

暴力递归:

优化:

动态规划1:

动态规划2:


题目:

牛牛准备参加学校组织的春游, 出发前牛牛准备往背包里装入一些零食, 牛牛的背包容量为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种情况。

思路分析:

暴力递归:

  1. 定义一个fun(int i,int rest)方法。
    1. 用从左到右尝试模型:fun(i, reat)返回在从左到右放到第i个物品时,有多少种可能性
  2. 当rest
  3. 当i越界时(i>arr.length),说明所有的物品已经放下,返回1
  4. 否则,返回i放进背包,或者不放进背包的可能性
    1. 如果i放进背包,则调用fun(i+1, rest-arr[i])
    2. 如果i不放进背包,则调用fun)i+1, rest)
    3. 将以上两种可能性相加作为返回值。

动态规划1:

  1. 在暴力递归中,一个函数都有两个参数,我们可以用这两个参数作为数组的索引
    1. 定义一个dp[][] = new int[i][rest]二维数组,dp[i][rset]表示从i到最后一个物品,背包剩余rest有多少种可能性
  2. 初始化:
    1. 首列:背包容量为0,说明么都不用放,可能性始终为1
    2. 末行:背包容量最后一个物品体积时,放或不放,可能性为2。
  3. 其余dp[i][j]:
    1. 当剩余容量 < 小于当前物品体积时,不放入背包,可能性为dp[i+1][j]。
    2. 当剩余容量 > 小于当前物品体积时,放入或者不放入,可能性相加:dp[i+1][j] + dp[i+1][ j-arr[i] ];
  4. 结果:
    1. dp[0][w]即为可能性结果。

动态规划2:

  1. 定义二维数组dp[][] = new int[n][w+1]
    1. dp[i][j]的含义是:第i个物品到第n-1个物品,抽出若干物品恰好组成总体积j的可能性。
  2. 初始化:
    1. 首列:背包容量为0,只有一种可能性,就是什么都不放入背包
    2. 末行:除了背包容量为0时。背包容量=最后一个物品体积时,可能性为1;否则为0
  3. 其余dp[i][j]:
    1. 当剩余容量 < 小于当前物品体积时,不放入背包,可能性为dp[i+1][j]。
    1. 当剩余容量 > 小于当前物品体积时,放入或者不放入,可能性相加:dp[i+1][j] + dp[i+1][ j-arr[i] ];
  1. 结果:
    1. 第一行的元素和,即为结果。

样例推到:

暴力递归:

动态规划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也会,所以就不再敲一遍了

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值