训练营第三十八天动态规划(01背包part1)

训练营第三十八天动态规划(01背包part1)

01背包理论基础

在这里插入图片描述

有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

二维dp数组01背包

题目

在这里插入图片描述

  1. 确定dp数组以及下标的含义:dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少

    在这里插入图片描述

  2. 确定递推公式

    1. 不放物品i:由dp[i - 1][j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是dp[i - 1][j]。(其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以背包内的价值依然和前面相同。

    2. 放物品i:由dp[i - 1][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]);

  3. dp数组如何初始化

    在这里插入图片描述

    for (int j = weight[0]; j <= bagweight; j++) {
        dp[0][j] = value[0];
    }
    
    1. 确定遍历顺序: 先遍历物品,然后遍历背包重量

      // weight数组的大小 就是物品个数
      for(int i = 1; i < weight.size(); i++) { // 遍历物品
          for(int j = 0; j <= bagweight; j++) { // 遍历背包容量
              if (j < weight[i]) dp[i][j] = dp[i - 1][j];
              else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
      
          }
      }
      
    2. 逆推

      在这里插入图片描述

      public class BagProblem {
          public static void main(String[] args) {
              int[] weight = {1,3,4};
              int[] value = {15,20,30};
              int bagSize = 4;
              testWeightBagProblem(weight,value,bagSize);
          }
      
          /**
           * 动态规划获得结果
           * @param weight  物品的重量
           * @param value   物品的价值
           * @param bagSize 背包的容量
           */
          public static void testWeightBagProblem(int[] weight, int[] value, int bagSize){
      
              // 创建dp数组
              int goods = weight.length;  // 获取物品的数量
              int[][] dp = new int[goods][bagSize + 1];
      
              // 初始化dp数组
              // 创建数组后,其中默认的值就是0
              for (int j = weight[0]; j <= bagSize; j++) {
                  dp[0][j] = value[0];
              }
      
              // 填充dp数组
              for (int i = 1; i < weight.length; i++) {
                  for (int j = 0; j <= bagSize; j++) {
                      if (j < weight[i]) {
                          /**
                           * 当前背包的容量都没有当前物品i大的时候,是不放物品i的
                           * 那么前i-1个物品能放下的最大价值就是当前情况的最大价值
                           */
                          dp[i][j] = dp[i-1][j];
                      } else {
                          /**
                           * 当前背包的容量可以放下物品i
                           * 那么此时分两种情况:
                           *    1、不放物品i
                           *    2、放物品i
                           * 比较这两种情况下,哪种背包中物品的最大价值最大
                           */
                          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("\n");
              }
          }
      }
      

一维滚动dp数组01背包(倒序)

题目(同二维dp数组01背包)

  1. 确定dp数组的定义 dp[j]表示:容量为j的背包,所背的物品价值最大为dp[j]。

  2. dp数组的递推公式 dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

  3. 一维dp数组如何初始化 初始都为0即可,因为dp[0]一定为0,后面都可以通过dp[0]来逐步得到

  4. 一维dp数组遍历顺序

    代码如下:

    for(int i = 0; i < weight.size(); i++) { // 遍历物品
        for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }
    

    **倒序遍历是为了保证物品i只被放入一次!**但如果一旦正序遍历了,那么物品0就会被重复加入多次!

    举一个例子:物品0的重量weight[0] = 1,价值value[0] = 15

    如果正序遍历

    dp[1] = dp[1 - weight[0]] + value[0] = 15

    dp[2] = dp[2 - weight[0]] + value[0] = 30

    此时dp[2]就已经是30了,意味着物品0,被放入了两次,所以不能正序遍历。

    为什么倒序遍历,就可以保证物品只放入一次呢?

    倒序就是先算dp[2]

    dp[2] = dp[2 - weight[0]] + value[0] = 15 (dp数组已经都初始化为0)

    dp[1] = dp[1 - weight[0]] + value[0] = 15

    所以从后往前循环,每次取得状态不会和之前取得状态重合,这样每种物品就只取一次了。

    再来看看两个嵌套for循环的顺序,代码中是先遍历物品嵌套遍历背包容量,那可不可以先遍历背包容量嵌套遍历物品呢?

    不可以!

    因为一维dp的写法,背包容量一定是要倒序遍历(原因上面已经讲了),如果遍历背包容量放在上一层,那么每个dp[j]就只会放入一个物品,即:背包里只放入了一个物品。

    倒序遍历的原因是,本质上还是一个对二维数组的遍历,并且右下角的值依赖上一层左上角的值,因此需要保证左边的值仍然是上一层的,从右向左覆盖。

  5. 验证

    在这里插入图片描述

     public static void main(String[] args) {
            int[] weight = {1, 3, 4};
            int[] value = {15, 20, 30};
            int bagWight = 4;
            testWeightBagProblem(weight, value, bagWight);
        }
    
        public static void testWeightBagProblem(int[] weight, int[] value, int bagWeight){
            int wLen = weight.length;
            //定义dp数组:dp[j]表示背包容量为j时,能获得的最大价值
            int[] dp = new int[bagWeight + 1];
            //遍历顺序:先遍历物品,再遍历背包容量
            for (int i = 0; i < wLen; i++){
                for (int j = bagWeight; j >= weight[i]; j--){
                    dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
                }
            }
            //打印dp数组
            for (int j = 0; j <= bagWeight; j++){
                System.out.print(dp[j] + " ");
            }
        }
    

46. 携带研究材料

题目

小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的空间,并且具有不同的价值。

小明的行李空间为 N,问小明应该如何抉择,才能携带最大价值的研究材料,每种研究材料只能选择一次,并且只有选与不选两种选择,不能进行切割。

输入描述

第一行包含两个正整数,第一个整数 M 代表研究材料的种类,第二个正整数 N,代表小明的行李空间。

第二行包含 M 个正整数,代表每种研究材料的所占空间。

第三行包含 M 个正整数,代表每种研究材料的价值。

输出描述

输出一个整数,代表小明能够携带的研究材料的最大价值。

输入示例
6 1
2 2 3 1 5 2
2 3 1 5 4 3
输出示例
5
提示信息

小明能够携带 6 种研究材料,但是行李空间只有 1,而占用空间为 1 的研究材料价值为 5,所以最终答案输出 5。

数据范围:
1 <= N <= 5000
1 <= M <= 5000
研究材料占用空间和价值都小于等于 1000

解答

二维数组
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        // 读取 N
        Scanner scanner = new Scanner(System.in);
        int M = scanner.nextInt();
        int N = scanner.nextInt();
        int[] costs = new int[M];
        int[] values = new int[M];

        for (int i = 0; i < M; i++) {
            costs[i] = scanner.nextInt();
        }
        for (int j = 0; j < M; j++) {
            values[j] = scanner.nextInt();
        }

        int[][] dp = new int[M][N + 1];
        
        for(int j = costs[0]; j <= N ; j++){
            dp[0][j] = values[0];
        }
        
        for(int i = 1 ; i < M; i++){//先遍历物品
            for(int j = 0; j <=  N ; j++){//后遍历重量
                if (costs[i] > j)
                    dp[i][j] = dp[i - 1][j];
                else
                    dp[i][j] = Math.max(dp[i - 1][j] , dp[i - 1][j - costs[i]] + values[i]);
            }
        }
        System.out.println(dp[M - 1][N]);
    }
}
一维数组
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        // 读取 N
        Scanner scanner = new Scanner(System.in);
        int M = scanner.nextInt();
        int N = scanner.nextInt();
        int[] costs = new int[M];
        int[] values = new int[M];

        for (int i = 0; i < M; i++) {
            costs[i] = scanner.nextInt();
        }
        for (int j = 0; j < M; j++) {
            values[j] = scanner.nextInt();
        }

        int[] dp = new int[N + 1];
        
        for(int i = 0 ; i < M; i++){//先遍历物品
            for(int j = N; j >= costs[i] ; j--){//后倒序遍历容量
                dp[j] = Math.max(dp[j],dp[j - costs[i]] + values[i]);
            }
        }
        System.out.println(dp[N]);
    }
}

416. 分割等和子集

力扣题目链接

题目

给你一个 只包含正整数非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

示例 1:

输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。

示例 2:

输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。

提示:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 100

解答

可以转化为01背包问题

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

五步法同01背包问题的五步法

题目中物品是nums[i],重量是nums[i],价值也是nums[i],背包体积是sum/2。

class Solution {
    public boolean canPartition(int[] nums) {
		int sum = Arrays.stream(nums).sum();

		if (sum % 2 != 0) return false;//不能整除
		
		int bagSize = Arrays.stream(nums).sum() / 2;
		int[] dp = new int[bagSize + 1];
		
		for (int i = 0; i < nums.length; i++) {
			for (int j = bagSize; j >= nums[i]; j--) {
				dp[j] = Math.max(dp[j],dp[j - nums[i]] + nums[i]);
			}

			if (dp[bagSize] == bagSize) return true;
		}
		return false;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值