一、二维数组

1、状态转移方程:

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

2、初始化

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

(2)状态转移方程 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 可以看出i 是由 i-1 推导出来,那么i为0的时候就一定要初始化。

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

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

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

for (int j = 0 ; j < weight[0]; j++) {
    dp[0][j] = 0;
}
// 正序遍历
for (int j = weight[0]; j <= bagweight; j++) {
    dp[0][j] = value[0];
}

(3)其他下标初始化:

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

dp[1][1] = max(dp[0][1], dp[0][1- weight[i]]    + value[i]) 一定在其左方且上方,

故一定初始化过了,可以设置为任何值,为了方便,现默认初始化为0。

二、一维(滚动数组)

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

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

2、初始化

(1)dp[0]=0,因为背包容量为0所背的物品的最大价值就是0。

(2)假设物品价值都是大于0的,为了不被初始值覆盖,所以dp数组初始化的时候,都初始为0

3、遍历顺序

倒序遍历

解释1:列表后面的值需要通过与上一层遍历得到的前面的值比较确定

解释2:01背包每个背包都只有一个,这个包放于不放与上一个包的状态有关,而左边的数组是描述上一个包状态的量,右边的数是当前包状态的一个待定,代码里是右边数组的确定需要左边的数,所以在计算出右边的数之前不能破坏左边的数。

解释3:对于二维,我每个新的dp数字的结果,都来源于上面和左边;其实一维也本该是这样,但一维的时候,如果还是从左到右,那么我其实左边的数字已经更新过了(左边已经不是“原来的”左边了!这会造成某些重复计算),即使上面还是原来的上面,得到的数字也是不正确的。而如果我们在背包层(里层)从右到左地遍历,那么左边的元素永远不会因为我前一次的操作被覆盖上新的值,这样可以得到正确的值。

三、应用

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

注意: 每个数组中的元素不会超过 100 数组的大小不会超过 200

示例 1:

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

分析:

物品是i,

重量是nums[i],价值也是nums[i],背包体积是sum/2。

leetcode-动态规划-01背包-LMLPHP

 类似于01背包

状态转移方程:

dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])
// 开始 01背包
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;

2、有一堆石头,每块石头的重量都是正整数。

每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

如果 x == y,那么两块石头都会被完全粉碎;

如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。

最后,最多只会剩下一块石头。返回此石头最小的可能重量。如果没有石头剩下,就返回 0。

示例:

  • 输入:[2,7,4,1,8,1]
  • 输出:1

分析:

尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小

那么分成两堆石头,一堆石头的总重量是dp[target],另一堆就是sum - dp[target]

在计算target的时候,target = sum / 2 因为是向下取整,所以sum - dp[target] 一定是大于等于dp[target]的

那么相撞之后剩下的最小石头重量就是 (sum - dp[target]) - dp[target]

for (int i = 0; i < stones.length; i++) {
//采用倒序
    for (int j = target; j >= stones[i]; j--) {
        dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
    }
}
//结果
sum - 2 * dp[target];

引自:代码随想录

07-12 11:45