背包问题小结

背包问题小结

介绍

学算法的时候,背包问题是一个很常见的动态规划问题,像什么01背包、完全背包、多重背包等,当时学的时候就有一些懵懵懂懂的,现在复习的时候又不会了,所以进行总结一下,方便日后查看学习。问题基本上都是lintcode上面的题目,然后在github上还找到一个专门讲背包问题的仓库:https://github.com/tianyicui/pack

背包问题

在n个物品中挑选若干物品装入背包,最多能装多满?假设背包的大小为m,每个物品的大小为A[i],题目地址:https://www.lintcode.com/problem/backpack/description

样例

样例 1:
	输入:  [3,4,8,5], backpack size=10
	输出:  9

样例 2:
	输入:  [2,3,5,7], backpack size=12
	输出:  12
	

挑战

O(n x m) 的时间复杂度 and O(m) 空间复杂度
如果不知道如何优化空间O(n x m) 的空间复杂度也可以通过.

注意事项

你不可以将物品进行切割。

代码

public class Solution {
    /**
     * @param m: An integer m denotes the size of a backpack
     * @param A: Given n items with size A[i]
     * @return: The maximum size
     */
    public int backPack(int m, int[] A) {
       if(A==null||A.length<=0||m<=0){
            return 0;
        }
		//当前容量下最大值
        int []dp = new int[m+1];


        for (int i = 0; i < A.length; i++) {
            //逆序
            for (int j = dp.length-1; j >=A[i]; j--) {
                dp[j] = Math.max(dp[j],dp[j-A[i]]+A[i]);          
            }
        }

        return dp[m];
    }
}

这是一个01背包问题,但是不是那种特别纯的01背包问题,这个背包没有价值,最终的目的是我们装的越多越好,所以我们可以把重量当作他的价值。01背包主要就是选择还是不选择这个物件的问题,在代码中我们先对物品的重量进行遍历,然后再对容量进行遍历,dp[j]表示当容量为j时,能够装得最满的量。对应的状态转移方程如下:
d p [ j ] = { d p [ j ] ( 不 选 择 A [ i ] ) d p [ j − A [ i ] ] + A [ i ] ( 选 择 A [ i ] ) dp[j]=\left\{ \begin{aligned} dp[j](不选择A[i]) \\ dp[j-A[i]]+A[i](选择A[i]) \end{aligned} \right. dp[j]={dp[j](A[i])dp[jA[i]]+A[i](A[i])

其实这个状态转移方程并不难得出来,我觉得难的是为什么使用一维数组是逆序遍历,而使用二维数组是正序遍历(当然二维数组逆序也可以)。

一维数组逆序遍历是因为如果采用正序遍历的时候,开始的遍历会影响到后续的遍历结果,就拿上面的样例1为例,当第一层遍历到最后一个物品,大小为5时,第二层循环从5-10遍历,当容量为5时,更新dp[5] = 5(这时5这个大小被使用),继续遍历到容量为10时,根据公式dp[10-5]+A[3]更新dp[10]=10(这时5这个大小又被使用),打破了只能使用一次的条件,所以采用从后向前遍历的方法,成功解决这个问题。

二维数组从前正序遍历,是因为dp[i] [j]使用的是上一轮遍历的结果,从下图的箭头也可以看出,这一轮的遍历根本不会对他产生影响。图来自https://www.cnblogs.com/mfrank/p/10533701.html这篇文章

image-20200701160333021

背包问题 II

n 个物品和一个大小为 m 的背包. 给定数组 A 表示每个物品的大小和数组 V 表示每个物品的价值.

问最多能装入背包的总价值是多大?,题目地址:https://www.lintcode.com/problem/backpack-ii/description

样例

样例 1:

输入: m = 10, A = [2, 3, 5, 7], V = [1, 5, 2, 4]
输出: 9
解释: 装入 A[1] 和 A[3] 可以得到最大价值, V[1] + V[3] = 9 

样例 2:

输入: m = 10, A = [2, 3, 8], V = [2, 5, 8]
输出: 10
解释: 装入 A[0] 和 A[2] 可以得到最大价值, V[0] + V[2] = 10

挑战

O(nm) 空间复杂度可以通过, 不过你可以尝试 O(m) 空间复杂度吗?

注意事项

  1. A[i], V[i], n, m 均为整数
  2. 你不能将物品进行切分
  3. 你所挑选的要装入背包的物品的总大小不能超过 m
  4. 每个物品只能取一次

代码

public class Solution {
    /**
     * @param m: An integer m denotes the size of a backpack
     * @param A: Given n items with size A[i]
     * @param V: Given n items with value V[i]
     * @return: The maximum value
     */
    public int backPackII(int m, int[] A, int[] V) {
        // write your code here
       
        if(A==null||A.length<=0||V==null||V.length<=0||A.length!=V.length||m<=0){
            return 0;
        }

        //容量
        int []dp = new int[m+1];


        for (int i = 0; i < A.length; i++) {
            //逆序遍历
            for (int j = dp.length-1; j >=A[i]; j--) {
                dp[j] = Math.max(dp[j],dp[j-A[i]]+V[i]);           
            }
        }
        return dp[m];
    }
}

这是一个01背包问题,和上题相比有了价值这个数组,但是思想一样,所以只需要一点小改动就解决了。

背包问题 III

描述

给出 n 个物品, 以及一个数组, nums[i]代表第i个物品的大小, 保证大小均为正数并且没有重复, 正整数 target 表示背包的大小, 找到能填满背包的方案数。
每一个物品可以使用无数次

样例

给出四个物品, 大小为 [2, 3, 5, 7], 价值为 [1, 5, 2, 4], 和一个大小为 10 的背包. 最大的价值为 15.

代码

package lintcode;

public class backPackIII {
      /**
     * @param m: An integer m denotes the size of a backpack
     * @param A: Given n items with size A[i]
     * @param V: Given n items with value V[i]
     * @return: The maximum value
     */
    public int BackPackIII(int m, int[] A, int[] V) {
        // write your code here

        if(A==null||A.length<=0||V==null||V.length<=0||A.length!=V.length||m<=0){
            return 0;
        }

        //容量
        int []dp = new int[m+1];


        for (int i = 0; i < A.length; i++) {
            //正序遍历
            for (int j = A[i]; j <dp.length; j++) {
                dp[j] = Math.max(dp[j],dp[j-A[i]]+V[i]);      
            }
        }
        return dp[m];
    }

    public static void main(String[] args) {
        backPackIII b = new backPackIII();
        int m=10;
        int[]A = {2,3,5,7};
        int[]V = {2,5,2,4};
        System.out.println(b.BackPackIII(m,A,V));
    }

}

这一题是一个完全背包问题,物品是无限多,因为这题是vip才能写的,所以我只能到网上搜这个题目自己写一下,需要注意的点是这里第二层循环采用正序遍历,因为物品时无穷多的。这一题还可以使用贪婪算法进行求解,就是找性价比最高的那个,我这里的实现方法是采用动态规划。

背包问题 IV

给出 n 个物品, 以及一个数组, nums[i]代表第i个物品的大小, 保证大小均为正数并且没有重复, 正整数 target 表示背包的大小, 找到能填满背包的方案数。
每一个物品可以使用无数次

样例

样例1

输入: nums = [2,3,6,7] 和 target = 7
输出: 2
解释:
方案有: 
[7]
[2, 2, 3]

样例2

输入: nums = [2,3,4,5] 和 target = 7
输出: 3
解释:
方案有: 
[2, 5]
[3, 4]
[2, 2, 3]

代码

public class Solution {
    /**
     * @param nums: an integer array and all positive numbers, no duplicates
     * @param target: An integer
     * @return: An integer
     */
    public int backPackIV(int[] nums, int target) {
        // write your code here
        if(nums==null||nums.length<=0||target<=0){
            return 0;
        }
		//代表次数
        int []dp = new int[target+1];
        dp[0] = 1;
        for (int i = 0; i < nums.length; i++) {
            //正序
            for (int j = nums[i]; j<dp.length; j++) {
                dp[j] += dp[j-nums[i]];
            }
        }
        return dp[target];
    }
}

这也是一个完全背包问题,但是又有点不同,是让我们找出填满背包的方案数,我们将dp设置为次数,然后发现这也是一个有最优子结构性质的问题。

背包问题 V

给出 n 个物品, 以及一个数组, nums[i] 代表第i个物品的大小, 保证大小均为正数, 正整数 target 表示背包的大小, 找到能填满背包的方案数。
每一个物品只能使用一次

样例

给出候选物品集合 [1,2,3,3,7] 以及 target 7

结果的集合为:
[7]
[1,3,3]
返回2

代码

public class Solution {
    /**
     * @param nums: an integer array and all positive numbers
     * @param target: An integer
     * @return: An integer
     */
    public int backPackV(int[] nums, int target) {
        // write your code here
        if(nums==null||nums.length<=0||target<=0){
            return 0;
        }

        int []dp = new int[target+1];
        dp[0] = 1;
        for (int i = 0; i < nums.length; i++) {
            //逆序
            for (int j = target; j>=nums[i]; j--) {
                dp[j] += dp[j-nums[i]];
            }
        }
        return dp[target];
    }
}

有了上面的经验我们只需要将背包问题IV的代码的第二层遍历改为逆序就解决了

总结

目前的问题就总结这么多,后面有再接着添加。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值