背包问题总结

本文深入探讨了01背包和完全背包问题,包括问题描述、解题思路及代码实现。01背包中,每种物品仅有一件,完全背包则允许每种物品有无限件。通过状态转移方程,我们能够解决这两种背包问题,找到装入物品的最大价值。此外,文章还介绍了如何将这些问题转化为其他类型的子问题,如分割等和子集,并提供了相应的代码示例。
摘要由CSDN通过智能技术生成

背包九讲


一、01背包

1.问题描述

已知一个背包最多能容纳体积之和为V的物品。现有 n 个物品,第 i 个物品的体积为 vi , 重量为 wi。求当前背包最多能装多大重量的物品?

2.题目地址

牛客在线编程

3.解题思路

01背包特点:每种物品仅有一件,可以选择放或者不放。
定义f[i][v]表示前i件物品放入一个容量为v的背包可以获得的最大价值,则状态转移方程为:

f[i][v] = max {f[i-1][v],f[i-1][V-v[i]]+w[i]}

即两种策略选择最大值:
第i件物品放入时,此时的价值为:前i-1件物品放入容量为v-v[i]的背包中的价值与该物品价值w[i]的和。
第i件物品不放入时,此时的价值为:前i-1件物品放入容量为v的背包中的价值。

空间复杂度的优化
由于f[i][v]是由f[i-1][v]和f[i-1][V-v[i]]推导而来,所以只需要在循环中以v=V…0的顺序推导f[v],就能保证f[v]时,f[V-v[i]]保存的是状态f[i-1][V-c[i]]的值。

4.代码
 /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     * 计算01背包问题的结果
     * @param V int整型 背包的体积
     * @param n int整型 物品的个数
     * @param vw int整型二维数组 第一维度为n,第二维度为2的二维数组,
     * 		     vw[i][0],vw[i][1]分别描述i+1个物品的vi,wi
     * @return int整型
     */
    public int knapsack (int V, int n, int[][] vw) {
        // write code here
        int[] dp = new int[V+1];
        //求最大的个数,初始化值都为0
        for(int i=0;i<n;i++){
            int v = vw[i][0];  //第i物品的体积
            int w = vw[i][1];  //第i物品的重量
            for(int j=V;j>=v;j--){
                if(j-v<0) continue;
                dp[j] = Math.max(dp[j],dp[j-v]+w);
            }
        }
        return dp[V];
    }
5.类似题
5.1 分割等和子集

题目描述:给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
题目地址leetcode416
解题思路:将该题转换为求解恰好装满体积为sum/2的背包的问题。当dp[sum/2]的值大于0时,证明存在一种划分可以把这两个数组分割成两个元素相等的子集。
代码

  public boolean canPartition(int[] nums) {
        int n = nums.length;
        int sum = 0;
        for(int i=0;i<n;i++){
            sum += nums[i];
        }
        if(sum%2!=0) return false;
        sum = sum/2;
        int[] dp = new int[sum+1];//装满体积为sum的背包,只要dp[sum]有值就说明分割成两个子集
        for(int i=1;i<sum+1;i++){
            dp[i] = Integer.MIN_VALUE; //初始化
        }
        for(int i=0;i<n;i++){
            int num = nums[i];
            for(int j=sum;j>=num;j--){
                dp[j] = Math.max(dp[j],dp[j-num]+num);
            }
        }
        return dp[sum]>0?true:false;
    }

二、完全背包

1.问题描述

有一个背包,最多能容纳的体积是V。现在有n种物品,每种物品有任意多个,第i种物品的体积为vi,
价值为wi。
1)求这个背包至多能装多大价值的物品?
2)求这个背包恰好装满,至多能装多大价值的物品?

2.题目地址

牛客在线编程

3.解题思路

完全背包特点:每种物品有无限件,该物品的策略是取0件,1件,2件…,如果按照0,1背包的思想。令f[i][v]表示前i件物品恰好放入一个容量为v的背包的最大价值,则状态转移方程应该为:

f[i][v] = max {f[i-1][v],f[i-1][V-v[i]*k]+w[i]*k} 其中0<=k*v[i]<=V

此时虽然有0(N*V)个状态需要求解,但是每一个状态的求解时间是k,而不是常数。
优化
对于完全背包而言,如果两件物品i、j满足v[i]<v[j]且w[i]<w[j],则去掉物品j,不进行考虑。在随机的情况下是可以大大减少物品的件数,但是最坏情况下的复杂度还是没有改善。
考虑01背包问题空间复杂度简化时,计算f[i]时,由于要保证第i件物品只选择了一次,所以遍历顺序是逆序(从V到0),但是完全背包是每件物品可以选n次,在选择f[i]时,f[i-v[i]]的值需要是可能已经加入了该物品的价值,所以必须采用顺序遍历(0…V)
背包问题中恰好装满的最大价值和求最大价值的问题
如题中的两种问法,实现起来主要是初始化的不同。

  • 如果是恰好装满背包,那么在初始化时除了f[0]为0,其他值为-∞。
  • 如果只希望价格尽量大,则初始值全部设为0。

因为初始化的数组的含义是,没有任何物品可以放入背包时的合法状态。

  • 如果背包要求恰好装满。那么此时只有容量为0的背包可以装满体积为0的背包,其他容量均没有合法解,所以设为-∞。
  • 如果背包不是必须装满,所有物品都不装时的价值为0,所以全部值都为0.
4.代码
   /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param v int整型 
     * @param n int整型 
     * @param nums int整型ArrayList<ArrayList<>> 
     * @return int整型ArrayList
     */
    public ArrayList<Integer> knapsack (int v, int n, ArrayList<ArrayList<Integer>> nums) {
        // write code here
        ArrayList<Integer> res = new ArrayList<>();
         //求解每个背包至少能装多大价值的物品
        int[] dp = new int[v+1]; 
        int maxValue = help(dp,v,n,nums);
       
        //求解必须装满背包的情况下的最大价值
        int[] dp1 = new int[v+1];
        for(int i=1;i<=v;i++){
            dp1[i] = Integer.MIN_VALUE; 
        }
        int value = help(dp1,v,n,nums);
        int fullValue = value >0?value:0;
        res.add(maxValue);
        res.add(fullValue);
        return res;
    }
    
    //完全背包问题的求解
    public Integer help(int[] dp,int V,int n,ArrayList<ArrayList<Integer>> nums ){
        for(int i=0;i<n;i++){
            ArrayList<Integer> list = nums.get(i);
            int v = list.get(0);
            int w = list.get(1);
            for(int j=v;j<=V;j++){
                dp[j] = Math.max(dp[j],dp[j-v]+w);
            }
        }
        return dp[V];
    }

总结

解题思路参考自背包九讲,背包的变形题很多,逐渐完善。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值