目录
对于动态规划的思路:一定要时刻记着5个步骤:1、确定dp数组以及下标的含义;2、确定递推公式;3、dp数组如何初始化;4、确定遍历顺序;5、打印dp数组
二维dp数组是基础,在此基础上可以优化为一维dp数组,所以要先弄清楚二维dp数组
dp[i][j]: 背包中物品的总价值
i 代表:物品遍历到物品 i
j 代表:目前背包的容量
要求递推公式,就是要求,当前背包中的最大值该怎么获得
有两个方向:
初始化dp数组的时候,一定要和dp数组的定义吻合
首先,当背包容量为0时,不论遍历到哪个物品,背包中的最大价值都是0,即dp[i][0] 全为0
其次,从递推公式中可以知道,dp[i][j] 的两种状态都是由 dp[i - 1][j] 来获取的,所以 dp[0][j]的状态一定要进行初始化,因为0-1为 -1,这种情况出界了

最后,是除左边界和右边界的所有情况的初始化,这些位置其实赋值任何都可以,因为在后面填充数组的过程中,其中的值还是会被 递推公式中的值所覆盖,跟本身初始化是什么值无关
这里遍历右两种情况:
这两种情况都是可以的,但是先遍历物品更好理解
因为dp[i][j] 每次都是获取正上方或者左上角的数据,不会被右方的数据影响
表格中的最后一个元素就是需要求的结果

最好根据递推公式手动推导一下这个表格就很清楚了
- 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 = 1; 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数组是对二维dp数组情况的优化,因为dp[i][j] 是在dp[i - 1][j] 的基础上得到的,所以完全可以只维护一维dp数组,可以理解成是一个滚动数组,每遍历一个物品,一维dp数组全部更新一次

一维数组:dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]
根据前面二维dp数组的递推公式来推导一下,一维dp数组的递推公式,还是两种情况:
对于dp[0] ,也就是 背包容量为 0 的时候,肯定应该赋值为 0 ,即 dp[0] = 0
但是对于非0下标应该怎么赋值呢,其实dp数组在推导的过程中一定是取最大数的,如果题目给的价值都是正整数,那么非0下标都初始化0就可以了
这样才能让dp数组在递推公式的过程中取的最大价值,而不是被覆盖(比如初始化100 就会被覆盖)
所以假设物品的价值都是大于0的,所以dp数组初始化的时候,都初始化成0 就可以了
这里的遍历顺序是从后向前进行遍历,因为dp[j - weight[i]]这种情况要用到上一个物品对应的数组前面的值
这种倒序遍历保证了物品 i 只被放入一次,如果从前向后遍历会造成物品被多次加入背包的情况
要清楚的是:
二维dp数组的时候是有两种写法的,一种是先遍历物品再遍历背包;另一种是先遍历背包再遍历物品,而且从前向后遍历,或者从后向前遍历都是可以的
一维dp数组是根据二维dp数组中的先遍历物品再遍历背包推导来的,而且每次获取下一个物品对应的dp[i][j]时会根据前面的dp[i-1][j-weight[i]]获得,这个数组是在当前层的左上方,所以当是一维数组的时候,移动要从后向前进行遍历,前面的数在这一层更新中还要继续使用呢
并且如果在一维数组中采用从前向后的遍历时,会导致物品被多次添加进背包,因为在确定下标大的元素的时候会用到下标小的元素,所以下标小的元素要最后才更新,所以要从后向前遍历
二维dp数组中,当前层是根据上一层来推导出来的,当前层的每一个值不受上一层的值得影响,两层得数据是完全隔离开来的,所以正序遍历和倒序遍历都可以
一维dp数组中,数组的内存空间是重复利用的,如果正序遍历,当前计算出来的值就会和旧值产生冲突

- public class BagProblem2 {
- 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[] dp = new int[bagSize + 1];
-
- // 初始化dp数组
- // 将dp[]数组初始化为0,默认就是,不用操作
-
- // 遍历填充dp数组
- // 外层循环代表几个物品,循环几次
- // 内层循环更换最大值
- for (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]);
- }
-
- // 打印dp数组
- System.out.print("物品" + i + "\t");
- for (int j = 0; j <= bagSize; j++) {
-
- System.out.print(dp[j] + "\t");
- }
- System.out.println("\n");
- }
-
- }
- }
题目链接:力扣
这道题目是可以使用回溯法的,只要找到集合中能够出现sum/2的子集总和,那就算分割成两个相同元素和子集了
但是使用回溯法会超时,直接使用动态规划,使用背包问题解决
既然是背包问题,先要判断是什么样的背包问题,如果其中的每个物品只能使用一次,那就是01背包,如果每个物品可以使用无数次,那就是完全背包
显然这道题目是01背包
既然是01背包问题,那我们需要明确,物品、物品重量、物品价值、背包容量
物品:数组中的元素
物品重量:元素大小
物品价值:元素大小
背包容量:数组和 / 2
1、确定dp[]数组的含义
使用一维dp[]数组,dp[j] 代表:当 数组和/2 为 j 时,数组中的某些元素相加可以 数组和/2
2、递推公式
两个方法可以获取:
所以递推公式为 dp[j] = max(dp[j],dp[j - nums[i]] + nums[i]);
3、初始化
只包含正整数,最小值为0,所以初始值默认为0
4、遍历顺序
因为这一轮数组的值是从上一轮的前方和获取的,所以需要从后向前遍历
最终代码
- class Solution {
- public boolean canPartition(int[] nums) {
-
- int sum = 0;
- for (int num : nums) {
- sum += num;
- }
-
- if ( sum % 2 != 0) {
- return false;
- }
-
- // 目标和为
- int target = sum / 2;
-
- // 创建dp[j]数组
- int[] dp = new int[target + 1];
-
- // 填充dp数组
- 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]);
- }
- }
-
- return target == dp[target];
-
- }
- }