代码随想录训练营day42, 01背包问题的二维数组和一维数组分析, 分割等和子集

接下来会开始不同的背包问题, 而01背包问题指的是, 有多个物品, 但是每个物品只能有一个, 这里的暴力解法就是回溯, 因为每种物品只有取与不取两种状态, 但是接下来会用dp降低时间复杂度

二维数组解法

  1. dp[i][j]的含义, [0,i]物品任取放入容量为[j]的背包
  2. 递推数组:
    1. 不放物品i, dp[i - 1][j], 意思就是背包此时已满, 到达最大价值, 但是i没有放进去
    2. 放物品i, dp[i - 1][j - weight[i]]+value[i], 背包容量没放i时的最大价值+i的价值
  3. 初始化: 当j为weight[0]时, dp[0][j]应该是value[0], 也就是当背包里只有第一个物品时, 重量也就只等于第一个物品
  4. 遍历顺序: 第一次for循环遍历
public class Main {
    public static void main(String[] args) {
        //先初始化一下题目
        int[] weight = {1, 3, 4};
        int[] value = {15, 20, 30};
        int bagSize = 4;
        solveProblem(weight, value, bagSize);
    }
    public static void solveProblem(int[] weight, int[] value, int bagSize){
        int wlen = weight.length;
        //创建dp数组
        int[][] dp = new int[wlen + 1][bagSize+1];
        //初始化dp
        //就记住当只放0号物品时, 背包重量就是weight[0]
        //这里是小于bagsize, 因为j就是定义的bag
        for (int j = weight[0]; j <= bagSize; j++){
            dp[0][j] = value[0];
        }

        //开始递推, 格外注意这里的小于等于号
        for(int i = 1; i < wlen; i++){
            for(int j = 1; j <= bagSize; j++) {
                //先来个判断, 如果这个背包的size比物品size还小了, 说明放不下
                //那么此时最优值就是没放这个物品的情况
                if(j < weight[i]){
                    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]);
                }
            }
        }
        //完成动态之后, 就把最后结果打印出来
        for(int i = 0; i < wlen; i++){
            for(int j = 0; j <= bagSize; j++){
                System.out.print(dp[i][j] + " ");
            }
            System.out.println("");
        }
    }
}

滚动一维数组(优先使用)

  1. dp[j]数组含义: 容量为j的背包最大价值为dp[j]
  2. 递推公式: dp[j] = Math.max(dp[j]), dp[j-weight[i]+value[i]) 不放物品i, 就是dp[j], 把上一层数组拷贝过来
  3. 初始化dp[0]=0, 就是背包容量为0时就是0
  4. 遍历顺序: 二维的时候背包容量是从小到大, 一维的时候遍历dp是从大到小倒序遍历, 保证每个数组只被遍历过一次
public class Main {
    public static void main(String[] args) {
        //先初始化一下题目
        int[] weight = {1, 3, 4};
        int[] value = {15, 20, 30};
        int bagSize = 4;
				solveProblem(weight, value, bagSize);
    }
    public static void solveProblem(int[] weight, int[] value, int bagSize){
        int wlen = weight.length;
        //创建一维dp数组
        int[] dp = new int[bagSize+1];
        //开始递推, 这里利用滚动数组
        for(int i = 0; i < wlen; i++){
						//这里的倒序尤其需要注意, 而且是大于等于weight
            for(int j = bagSize; j >= weight[i]; j--) {
                    dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
                }
            }
        //完成动态之后, 就把最后结果打印出来
        for(int j = 0; j <= bagSize; j++){
            System.out.println(dp[j] +" ");
        }
    }
}

分割等和子集

其实根据题目可以看出来可以用回溯, 但是会超时, 所以重点就是把这道题和背包联系起来:

  1. 背包的体积为sum
  2. 背包要放入的商品重量为元素的数值, 价值也为元素的数值
  3. 背包如果正好装满, 说明找到了总和为sum/2的子集
  4. 背包中的每一个元素是不可重复放入

(相当于一个容量为sum/2的背包, 尝试数字是否能全部放进这个背包里, 物品的重量和价值都是nums[i])

动规五部曲:

  1. dp[j], 容量为j的背包
  2. 递推公式, 和之前一样
  3. 初始化: 非0下标的元素初始化为0就可以了
  4. 遍历顺序: 由于这里还是滚动数组, 所以注意下j的遍历
class Solution {
    public boolean canPartition(int[] nums) {
        //首先可以剪个枝
        if(nums == null || nums.length == 0){
            return false;
        }
        int sum = 0;
        int nlen = nums.length;
        //先求出sum
        for(int num : nums){
            sum += num;
        }
        //特判: 如果是奇数的话就不能平分
        if(sum % 2 != 0) {
            return false;
        }
        //求出target, 也就是sum/2
        int target = sum/2;
        int[] dp = new int[target + 1];
        //开始递推
        for(int i = 0; i < nlen; i++){
            for(int j = target; j >= nums[i]; j--){
                dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
            }
        }
        return dp[target] == target;
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值