代码随想录DAY35|01背包问题、416. 分割等和子集

1. 01背包问题

卡码
求背包装满时最大价值

背包问题只用掌握01背包、完全背包即可,最多加上一个多重背包。
在这里插入图片描述
动规五部曲

  1. 确定dp数组以及下标的含义
    i表示将物品i放进背包,j表示背包容量。dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
    在这里插入图片描述
  2. 确定递推公式
    求一个状态的价值有两种方式:拿该物品,不拿该物品,也就是说dp[i][j]是由两个方向决定的。
    以dp[1][4]举例。如果不拿该物品,推导方向如下,是由上一个状态dp[i-1][j]决定的。
    在这里插入图片描述
    如果拿物品1,则要先预留出物品1的空间,获得那个状态下(dp[0][1])背包的总价值,再加上物品1的价值,推导方向为:
    在这里插入图片描述

以上过程,抽象化如下:

不放物品i:背包容量为j,里面不放物品i的最大价值是dp[i - 1][j]。

放物品i:背包空出物品i的容量后,背包容量为j - weight[i],dp[i - 1][j - weight[i]] 为背包容量为j - weight[i]且不放物品i的最大价值,那么dp[i - 1][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值

递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

  1. dp数组如何初始化
    关于初始化,一定要和dp数组的定义吻合,否则到递推公式的时候就会越来越乱。首先背包容量为0时dp[i][0]一定为0。然后根据递推公式可得当前i是由i-1那一行推导得来的,那么i=0时一定要初始化,即dp[0][j]。当 j < weight[0]的时候,dp[0][j] 应该是 0,因为背包容量比编号0的物品重量还小。当j >= weight[0]时,dp[0][j] 应该是value[0],因为背包容量放足够放编号0物品。
    在这里插入图片描述
    对于其他值,只要初始化的足够小让递推过程中可以被覆盖即可。如果题中给的都是非负数那么可初始化为0。
  2. 确定遍历顺序
    先遍历物品,再遍历背包重量。递推公式中,dp[i][j]都是从左上角的值求出来的,那么先遍历背包重量也可以,不影响递推。
  3. 举例推导dp数组
    如果不理解可以自己模拟一下递推过程。
import java.util.Scanner;
 
public class Main{
    public static void main (String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int bagweight = sc.nextInt();
        int[] weight = new int[n];
        int[] value = new int[n];
        for(int i = 0; i < n; i++){
            weight[i] = sc.nextInt();
        }
        for(int i = 0; i < n; i++){
            value[i] = sc.nextInt();
        }
         
        int[][] dp = new int[n][bagweight + 1];
         
        for(int i = weight[0]; i <= bagweight; i++){
            dp[0][i] = value[0];
        }
        for(int i = 1; i < n; i++){
            for(int j = 0; j <= bagweight; j++){
                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]);
                }
            }
        }
        System.out.println(dp[n - 1][bagweight]);
         
    }
}

01背包用滚动数组(一维数组)解决

观察递推公式,每一行的值都是由上一行推导出来的,而且都是求最大值,最终结果最大值一定是右下角。那么可以将上一行的内容复制到本行,然后直接在本行进行推导,即:
dp[i][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i]);

与其把dp[i - 1]这一层拷贝到dp[i]上,不如只用一个一维数组了,只用dp[j](一维数组,也可以理解是一个滚动数组)。这就是滚动数组的由来,需要满足的条件是上一层可以重复利用,直接拷贝到当前层。去掉dp[i]这个维度,那么递推公式就变成:
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

依然是动规五部曲,dp[j]的定义是容量为j的背包最大价值。初始化时dp[0]一定是0,其余元素仍然保持最小即可。注意遍历顺序,在遍历背包重量时要从大到小遍历。因为递推公式要用到左边的值,从大到小遍历就可以保证递推时仍然使用的是上一层的数据(即dp[i-1][j]),否则就会多次使用前面的值。可以自己手写一遍,就会发现前面的物品被多次装进背包,不符合01背包的要求。

import java.util.Scanner;

public class Main{
    public static void main (String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int bagweight = sc.nextInt();
        int[] weight = new int[n];
        int[] value = new int[n];
        for(int i = 0; i < n; i++){
            weight[i] = sc.nextInt();
        }
        for(int i = 0; i < n; i++){
            value[i] = sc.nextInt();
        }
        
        int[] dp = new int[bagweight + 1];
        
        for(int i = 0; i < n; i++){
            for(int j = bagweight; j >= weight[i]; j--){
                dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
            }
        }
        System.out.println(dp[bagweight]);
        
    }
}

2. 分割等和子集

力扣
背包问题不仅可以求最大价值,也可以求一个背包能不能装满

要求数组中是否有某些元素加起来刚好等于sum/2,可以用背包问题模拟。每个元素就是一个物品,重量就等于价值当物品装满时,如果最大价值刚好等于重量,即dp[target]==target时,说明正好可以装满。

class Solution {
    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;
        int target = sum/2;
        int[] dp = new int[target + 1];
        for(int i = 0; i < n; 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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值