代码随想录 动态规划||01背包理论 416
迪丽瓦拉
2024-05-30 00:38:30
0

Day36

01背包理论基础

01背包

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

  • 暴力的解法是指数级别的时间复杂度。进而才需要动态规划的解法来进行优化

  • 背包最大重量为4。

物品为:

重量

价值

物品0

1

15

物品1

3

20

物品2

4

30

问背包能背的物品最大价值是多少?

二维dp数组01背包

  • 确定dp数组以及下标的含义

  • dp[i] [j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。

  • 确定递推公式

  • 那么可以有两个方向推出来dp[i] [j],

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

  • 放物品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]);

  • dp数组如何初始化

  • 如果背包容量j为0的话,即dp[i] [0],无论是选取哪些物品,背包价值总和一定为0。

  • dp[0] [j],即:i为0,存放编号0的物品的时候,各个容量的背包所能存放的最大价值。

那么很明显当 j < weight[0]的时候,dp[0] [j] 应该是 0,因为背包容量比编号0的物品重量还小。

当j >= weight[0]时,dp[0] [j] 应该是value[0],因为背包容量放足够放编号0物品。

  • 其他下标初始为什么数值都可以,因为都会被覆盖

  • 确定遍历顺序

  • 都可以,但是先遍历物品更好理解

public class 零一背包 {public static void main(String[] args) {int[] weight = {1,3,4};int[] value = {15,20,30};int bagSize = 4;testWeightBagProblem(weight,value,bagSize);//0    15    15    15    15    //0    15    15    20    35    //0    15    15    20    35}public static void testWeightBagProblem(int[] weight, int[] value, int bagSize) {int[][] dp = new int[weight.length][bagSize + 1];//初始化dp数组for (int j = weight[0]; j <= bagSize; j++){//当背包容量为0的时候,dp[i][0] = 0dp[0][j] = value[0];//这里处理的是dp[0][j],当容量比第一个物品重量大,才不为零}for (int i = 1; i < weight.length; i++){for (int j = 1; j <= bagSize; j++){if (j < weight[i]){//如果第i个物品的重量比背包容量都大,一定放不进去dp[i][j] = dp[i - 1][j];}else{//取不放第i个物品,和放第i个物品(但有可能要把之前放进去的物品取出)这两种情况最大值dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);}}}for (int i = 0; i < weight.length; i++) {for (int j = 0; j <= bagSize; j++) {System.out.print(dp[i][j] + "\t");}System.out.println();//打印dp数组}}
}

01背包理论基础(滚动数组)

一维dp数组(滚动数组)

  • 对于背包问题其实状态都是可以压缩的。

  • 在使用二维数组的时候,递推公式:dp[i] [j] = max(dp[i - 1] [j], dp[i - 1] [j - weight[i]] + value[i]);

  • 其实可以发现如果把dp[i - 1]那一层拷贝到dp[i]上,表达式完全可以是:dpi = max(dpi, dpi] + value[i]);

与其把dp[i - 1]这一层拷贝到dp[i]上,不如只用一个一维数组了,只用dp[j](一维数组,也可以理解是一个滚动数组)。

  • 这就是滚动数组的由来,需要满足的条件是上一层可以重复利用,直接拷贝到当前层。

  • 确定dp数组的定义

  • 在一维dp数组中,dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]。

  • 一维dp数组的递推公式

  • dp[j]为 容量为j的背包所背的最大价值,那么如何推导dp[j]呢?

  • dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

  • 一维dp数组如何初始化

  • dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j],那么dp[0]就应该是0,因为背包容量为0所背的物品的最大价值就是0。

  • 如果题目给的价值都是正整数那么非0下标都初始化为0就可以了

  • 一维dp数组遍历顺序

  • 倒序遍历是为了保证物品i只被放入一次

public class 一维背包 {public static void main(String[] args) {int[] weight = {1, 3, 4};int[] value = {15, 20, 30};int bagSize = 4;testWeightBagProblem(weight, value, bagSize);//0    15    15    20    35    }public static void testWeightBagProblem(int[] weight, int[] value, int bagSize) {int[] dp = new int[bagSize + 1];//dp数组容量为bagSize + 1for (int i = 0; i < weight.length; i++){//遍历物品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.print(dp[j] + "\t");}}
}

416. 分割等和子集

力扣题目链接

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

思路

  • 可以转换成01背包问题

  • 每个元素都是物品,重量和价值都是nums[i],背包容量是sum / 2

  • 看背包能不能正好装满

代码

class Solution {public boolean canPartition(int[] nums) {int sum = 0;for (int num : nums) {sum += num;//先计算数组元素的和}if (sum % 2 == 1) return false;//如果和是奇数,那就无法分割int target = sum / 2;//转换成要从数组中找元素,这些元素和能不能正好是targetint[] dp = new int[target + 1];//背包容量是target,看能不能装满for (int i = 0; i < nums.length; i++){//遍历数组,就是物品,每个物品的重量和价值都是nums[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;//最后要看看能不能正好装满}
}

相关内容