背包问题及其变体(Java)

10 篇文章 0 订阅
10 篇文章 0 订阅

背包问题:

最经典的0-1背包
背包问题是有一个一定容量的背包,然后有一堆物品,这些物品有一定的重量,要求在不超过背包容量的情况下,装最多的重量的问题。

背包问题的变体
背包问题有很多的变体,主要有以下几个:

  • 物品不仅仅有重量还会对应不同的价值,求价值最高,而不是重量最高
  • 每个物品有无限个而不是只有一个
  • 不是求最大重量,而是求达到重量的组合的个数

背包问题的解法:

背包问题是用动态规划的方法解决,可以构造一个二维动规数组解决问题,而二维数组的做法可以进一步优化空间复杂度变为一维的动规数组。

动态规划的问题主要要解决三个问题,1、dp数组的设计,2、边界情况,3、递推公式。

二维动规划数组的解法

  • 动态规划数组设置:设置大小为dp[items.length + 1][backpackSize + 1]的boolean数组,其中数组中每个元素dp[i][j]表示只用前i个物品,能否达到j的负重。+1是用于设置边界条件。
  • 边界情况:边界条件是在考虑0件物品 和 背包负重为0 时最大负重为0。
  • 递推公式:
    • dp[i][j] = dp[i - 1][j] 只要用前i-1个物品能达到 j 重量,那用上物品i,也能达到j重量
    • j >= items[i-1] && dp[i-1][j - items[i-1]] 时,dp[i][j] 设置为true
      保证背包的负重大于物品i(这里-1是因为0用于设置边界,从1开始),证明当前负重能放下物品i,如果在不用i物品时,可以达到负重j减物品i的重量,那么用上了物品i,就可以达到j的重量。
  • 具体的解法看下题 lintcode 92

一维度数组
也是考虑动规数组的设计、边界情况、递推公式三个问题,二维数组优化为一维,是滚动重复运用了一维数组,达到二维的效果,它们都要两重循环,时间是一样的。

  • 动规数组:设置大小为dp[backpackSize + 1]的boolean数组,dp[j]表示:能否达到j负重
  • 边界情况:dp[0] = 0
  • 递推公式:同样是遍历各个物体i,在只考虑i-1的物品时,如果能达到dp[j - items[i]],那么考虑了物品i,可以达到负重j。
    这里注意,因为每个物品只能用一次,所以应该从最大负重往前递推,如果从前往后递推,物品就可能被使用了多次。
  • 同样结合lintcode92 看完整的解法

背包变体的解法

  • 带价值的物品:递推数组不要设置为boolean,设置为int数组,表示价值
    • 二维:dp[i][j]表示,只考虑前i的物品,负重不超过j的情况下,价值最高的值
    • 一维:dp[j] 表示:不超过负重j的情况下,价值最高的值得
    • 结合lintcode 125 看完整解法
  • 物品不是只有一个,而是有无限个:只需要把递推的方向反转,刚刚提到最大负重往前递推,防止多次使用到物体,只要正向递推,就可以解决无限个物体的问题,具体看lintcode 440 完整解法
  • 求组合个数:只要把动规数组的值,设置为达到这个重量的组合个数,边界情况dp[0]=1 即可,具体看 lintcode 563

lintcode 92 背包问题

这是经典的01背包问题:

二维数组的动态规划解法:

public int backPack(int backpackSize, int[] items) {

        // 由于要从背包为0,物体数为0的状态开始,所以dp数组要多一行的维度
        boolean dp[][] = new boolean[items.length + 1][backpackSize + 1];

        for (int i = 0; i <= items.length; i++) {
            for (int j = 0; j <= backpackSize; j++) {
                dp[i][j] = false;
            }
        }

        // dp[i][j] 表示 前i个背包,随机选若干个,能不能达到j的重量
        dp[0][0] = true;
        for (int i = 1; i <= items.length; i++) {
            for (int j = 0; j <= backpackSize; j++) {
                dp[i][j] = dp[i - 1][j];
                if (j >= items[i-1] && dp[i-1][j - items[i-1]]) {
                    dp[i][j] = true;
                }
            }
        }

        for (int i = backpackSize; i >= 0; i--) {
            if (dp[items.length][i]) {
                return i;
            }
        }

        return 0;
    }

一维滚动数组解法:

public int backPack(int backpackSize, int[] items) {
        
        // dp[i]代表,容量为 i 的背包,最否能装到负重j
        boolean dp[] = new boolean[backpackSize + 1];

		dp[0] = true;
        for(int i = 0; i < items.length; i++) {
            for(int j = backpackSize; j >= items[i]; j--) {
                if(dp[j - items[i]]){
                	dp[j] = true;
                }
            }
        }
        
        for(int j = backpackSize; j >= 0; j--){
        	if(dp[j]){
        		return j;
        	}
        }
        return 0;
    }

lintcode 125 带价值的背包问题

这是01背包中物品带有价值的变体

只要把01背包问题中,dp数组的值设置为当前最大价值,而非boolean能否得到。稍微改动即可

二维数组解法:

public int backPackII(int backpackSize, int[] items, int[] itemsValues) {
        // write your code here

        int[][] dp = new int[items.length + 1][backpackSize + 1];

        for(int i = 0; i <= items.length; i++){
            for(int j = 0; j <= backpackSize; j++){
                if(i == 0 || j == 0){
                    dp[i][j] = 0;
                }else if(items[i-1] > j){
                    dp[i][j] = dp[i-1][j];
                }else{
                    dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-items[i-1]] + itemsValues[i-1]);
                }

            }
        }

        return dp[items.length][backpackSize];
    }

一维滚动数组:

public int backPackII2(int backpackSize, int[] items, int[] itemsValues) {
        // write your code here

        int[] dp = new int[backpackSize + 1];

        for(int i = 0; i < items.length; i++){
            for(int j = backpackSize; j >= items[i]; j--){
                if(dp[j] < dp[j - items[i]] + itemsValues[i]){
                    dp[j] = dp[j - items[i]] + itemsValues[i];
                }
            }
        }

        return dp[backpackSize];
    }

lintcode 440. 背包问题 III

这是01背包问题物体可以用无限次,且带有价值的变体

dp数组为当前最大价值,递推公式从前往后递推,无限次使用物品即可。

一维滚动数组

public int backPackIII(int[] items, int[] itemsValues, int backpackSize) {

        int[] dp = new int[backpackSize + 1];

        for(int i = 0; i < items.length; i++){
            for(int j = items[i]; j <= backpackSize; j++){
                if(dp[j] < dp[j - items[i]] + itemsValues[i]){
                    dp[j] = dp[j - items[i]] + itemsValues[i];
                }
            }
        }

        return dp[backpackSize];
    }

lintcode 563. 背包问题 V

这是求达到target负重的组合个数,物品只能用一次

只要边界情况设置为1,然后把dp的值,变为当前的组合个数,递推时不是是否能达到的boolean,而是累加上前状态的个数即可。

public int backPackV(int[] nums, int target) {
        // Write your code here
        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];
    }

lintcode 562. 背包问题 IV

这是求达到target负重的组合个数,物品只能用无限次

只要只能用一次的稍微修改,改为从前向后递推即可。

public int backPackIV(int[] nums, int target) {
        // write your code here
        int[] dp = new int[target + 1];
        
        dp[0] = 1;
        for(int i = 0; i < nums.length; i++){
            for(int j = nums[i]; j <= target; j++){
                dp[j] += dp[j - nums[i]];
            }
        }
        
        return dp[target];
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值