Day36 动态规划:0-1背包理论基础(一) 0-1背包理论基础(二) 416.分割等和子集

106 阅读3分钟

0-1背包理论基础(一)

要先知道暴力解法

然后用动态规划来解决,相对于暴力解法,DP在性能上提高了多少。

1️⃣ 确定 dp数组 以及 下标 的含义

dp[i][j] :从下标为 [0 ~ i] 之间的物品里任取,放进容量为 j 的背包,价值总和最大是多少。

(后面在写递推公式,初始化,确定遍历顺序都是紧紧围绕着dp数组的含义)

2️⃣ 确定递推公式

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

  • 不放物品i:背包容量为j,所能装的最大价值是 dp[i - 1][j]
  • 放物品i:放物品i的最大价值是 背包容量减去物品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]);

可以这么理解:放了物品i,还剩下j - weight[i]的容量可以放其他物品,剩下空间能得到的最大值是dp[i-1][j-weight[i]]

这里的意思是:你放了物品 i 之后剩下的空间(就是他说的不放物品i的空间)能放的最大价值。

先把物品i放进去,则还剩j-weight的容量,这些容量用来放前i-1个

3️⃣ dp数组如何初始化

非0下标初始化成什么都可以,都不影响最后求 dp[i][j] 的值。

4️⃣ 确定遍历顺序

对于二维dp数组实现的0-1背包问题,2层for循环是可以颠倒的。

5️⃣ 举例推导dp数组

0-1背包理论基础(二)

网友:应该是把 dp[i][j] 覆盖到 dp[i-1][j] 那里

网友:就像爬楼梯中维护的一维dp数组,实际有用的就前两个元素,就可以简化为只维护两个元素,而不是一个数组,这里也是同理。

网友:只有一个数组,实际上自身拷贝自身,虽然数值相同但表示的意义不同相同

递推公式:

  • 不放物品i:dp[j]
  • 放物品i:dp[j - weight[i] + value[i]]

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

不放物品就表示不覆盖原来的数据

dp数组的初始化:

首先,背包容量不能是负数。(若容量是负数,就不在背包问题的讨论范围内了。)

若背包容量是正数,应该初始化为多少?

由递推公式:当前值 = max (本身,前一个值 + value)

遍历顺序:

倒序遍历,保证每个物品只被添加过一次。

⚠️只能先遍历物品,再遍历背包

在一维dp数组中,把一个矩阵压缩成一维数组,数据滚动利用,我们使用倒序来保证每个物品添加一次,

如果先遍历背包,再遍历物品,这样dp数组里面记录的就都是一个物品的数值了。

(不理解,可以画表格解释,很直观)

用代码,把dp数组打印出来。

416.分割等和子集

题目链接:416.分割等和子集

难度指数:😀😐😕😟

AC代码: (核心代码模式)

 class Solution {
 public:
     bool canPartition(vector<int>& nums) {
         int sum = 0;
 ​
         //d[i]中的i表示背包内总和
         //题目中说:每个数组中的元素不会超过100,数组的长度不会超过200
         //因此总和不会大于20000,背包最大只需要其中的一半,所以10001就可以了
         vector<int> dp(10001, 0);
         for (int i = 0; i < nums.size(); i++) {
             sum += nums[i];
         }
         if (sum % 2 == 1) {
             return false;
         }
         int target = sum / 2;
 ​
         //开始0-1背包
         for (int i = 0; i < nums.size(); i++) {
             for (int j = target; j >= nums[i]; j--) {  //每个元素一定是不可重复放入,所以从大到小遍历
                 dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
             }
         }
         //集合中的元素正好可以凑成总和target
         if (dp[target] == target) return true;
         return false;
     }
 };