【代码训练营】day41 | 01背包问题 & 416. 分割等和子集

所用代码 java

01背包理论

背包最大重量为:4

重量价值
物品0115
物品1320
物品2430

暴力:O(2^n)

动态规划:

1、二维dp数组 dp[i] [j]

  • dp数组含义:[0, i]物品,任取放进容量为j的背包里的最大价值

  • 递推公式:

    • 不放物品i :dp[i-1] [j]
    • 放物品i:dp[i-1] [j - weight[i]] + value[i]

    dp[i] [j] = Math.max(dp[i-1] [j], dp[i-1] [j - weight[i]] + value[i]

  • 初始化:

    dp[i] [j] i:物品 j:背包容量(0 1 2 3 4 )

01234
物品0015151515
物品10n => 由上和左上推出
物品20
  • 遍历顺序:对于二维数组的01背包,先背包后物品,先物品后背包都可以
  • 打印dp数组:

具体代码:

public class 背包01 {
    public static void main(String[] args) {
        int[] weight = {1,3,4};
        int[] value = {15,20,30};
        int bagSize = 4;
        bagProblem01(weight,value,bagSize);
    }

    public static void bagProblem01(int[] weight, int[] value, int bagSize){
        int goods = weight.length; // 物品数量
        int[][] dp = new int[goods][bagSize + 1];

        // 初始化 - 只有一个物品的时候,不管背包多大,加载都是该物品的价值
        for (int j = 0; j <= bagSize; j++) {
            dp[0][j] = value[0];
        }
        // 背包i在背包容量为0的时候,最大价值为0
        for (int i = 0; i < goods; i++) {
            dp[i][0] = 0;
        }

        // 先遍历背包,再遍历物品
        for (int i = 1; i < goods; i++) {
            for (int j = 1; j <= bagSize; j++) {
                if (j < weight[i]){
                    // 当背包容量没有当我物品那么大时,就是放不下物品,最大的重量为i-1时最大重量
                    dp[i][j] = dp[i-1][j];
                }else {
                    // 可以放下物品时:比较放物品后的价值和没放物品价值谁大
                    dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i]);
                }
            }
        }

        // 打印dp数组
        for (int i = 0; i < goods; i++) {
            for (int j = 0; j <= bagSize; j++) {
                System.out.print(dp[i][j] + "\t");
            }
            System.out.println();
        }
    }
}
打印结果:
0	15	15	15	15	
0	15	15	20	35	
0	15	15	20	35

2、一维dp数组dp[j] - 滚动数组

  • dp[j]:容量为j的背包最大价值为dp[j] i为物品
  • 递推公式:dp[j] = max(dp[j], dp[j-weight[i]] + value[i])
  • 初始化:dp[0] = 0 其他非负里面的最小值,如0。
  • 遍历顺序:
    • 先遍历物品,再遍历背包
    • 倒序遍历,保证每个物品只被添加了一次。
  • 打印dp数组
public static void bagProblem1(int[] weight, int[] value, int bagSize){
    int goods = weight.length; // 物品数量
    int[] dp = new int[bagSize + 1];
    // 初始化
    Arrays.fill(dp, 0);
    // 先遍历背包,再遍历物品
    for (int i = 0; i < goods; i++) {
        // j >= weight[i] 保证从右向左遍历的时候dp[j-weight[i]]不会越界
        for (int j = bagSize; j >= weight[i]; j--){
            dp[j] = Math.max(dp[j-1], dp[j-weight[i]] + value[i]);
        }
    }
    // 打印
    for (int MaxValue : dp) {
        System.out.print(MaxValue + "\t");
    }
}

分割等和子集 LeetCode 416

题目链接:分割等和子集 LeetCode 416 - 中等

思路

试了滑动窗口,不行。滑动窗口适合连续的数。


应该抽象为一个背包,背包的容量为数组和的一半,只要装下一半,另一半肯定可以。

  • dp[j] :容量为j的背包能装下的最大价值为dp[j]
  • 递推公式:dp[j] = max(dp[j], dp[j-nums[i]] + nums[i]) 重量和价值是相同的
  • 初始化:dp[0] = 0;背包不可能是负数,需初始成非负整数的最大值 0
  • 遍历方向:从右到左
  • 打印dp数组

一维数组:

class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        if (sum % 2 == 1) return false;
        // target 相当于背包的容量
        int target = sum / 2;
        // dp数组初始为0
        int[] dp = new int[target + 1];
        // 物品
        for (int i = 0; i < nums.length; i++) {
            for (int j = target; j >= nums[i]; j--){
                dp[j] = Math.max(dp[j], dp[j-nums[i]] + nums[i]);
            }
        }
        // 看一下最后一个数是不是target
        return dp[target] == target;
    }
}

二维数组:

  • dp[i] [j] : 前i个数,其总价值不超过j的最大价值为dp[i] [j] j代表的是背包重量
  • 递推公式:dp[i] [j] = Math.max(dp[i-1] [j], dp[i-1] [j - nums[i]] + nums[i]);
  • 初始化:小于num[0]的不能装,初始化为0,其余为nums[0]
class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        if (sum % 2 == 1) return false;
        // target 相当于背包的容量
        int target = sum / 2;

        int[][] dp = new int[nums.length][target + 1];
        // 初始化,只有一个物品时的重量
        for (int j = 0; j <= target; j++) {
            dp[0][j] = j > nums[0] ? nums[0] : 0;
        }
        // 物品数量
        for (int i = 1; i < nums.length; i++) {
            // 背包容量,背包容量0,默认不能装
            for (int j = 1; j <= target; j++) {
                // 不能装下物品,最大值就是前一个值
                if (j < nums[i]){
                    dp[i][j] = dp[i-1][j];
                }else {
                    dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j - nums[i]] + nums[i]);
                }
//                System.out.print("dp="+dp[i][j] + "\t");
            }
//            System.out.println();
        }
        return dp[nums.length-1][target] == target;
    }
}

总结

本题主要是分析,只要把该题分析出来是背包问题的话,照着这个思路就非常的简单。

第二点就是二维数组在初始化的适合需要注意,背包重量小于nums[0]的数是没法装的,需初始化为0。

另外还有一种思路:

  • dp[i] [j] : 从[0, i]区间中挑选一些正整数,每个数只能用一次,这些数的和恰好为 j
  • 递推公式:dp[i] [j] = dp[i - 1] [j] || dp[i - 1] [j - nums[i]]
  • 初始化:在num[0] < target 的情况下,第一个数只能让容积为自己的背包填满:dp[0] [num[0]] = true
class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0;
        for (int num : nums) sum+=num;
        if (sum % 2 == 1) return false;
        // target 相当于背包的容量
        int target = sum / 2;

        boolean[][] dp = new boolean[nums.length][target + 1];
        // 初始化,填满第一行的dp[0][nums[0]] 只有nums[0]本身
        if (nums[0] < target) dp[0][nums[0]] = true;

//        System.out.print("dp[0] = " + Arrays.toString(dp[0]));
//        System.out.println();

        // 外层遍历物品数量
        for (int i = 1; i < nums.length; i++) {
            // 内层遍历背包
            for (int j = 0; j <= target; j++) {
                // 先取上一次的结果,后面进行修正
                dp[i][j] = dp[i-1][j];

                // 如果某个物品的重量恰好等于背包重量,则满足条件
                // 所以这一列的值都为true
                if (nums[i] == j){
                    dp[i][j] = true;
//                    System.out.print("***="+dp[i][j] + "\t\t");
                    continue;
                }

                // 如果该物品小于背包的重量,就判断能否加入新的物品
                // dp[i-1][j]表示不放入背包,如果前面有满足条件,后面就一定可以满足
                // dp[i-1][j-nums[i]]表示放入背包,放入后判断放入背包的区间有没有满足条件的,就是左上
                if (nums[i] < j){
                    dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]];
                }
//                System.out.print("dp="+dp[i][j] + "\t\t");

            }
//            System.out.println();
        }
        return dp[nums.length-1][target];
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值