leetcode-动态规划-01背包

一、二维数组

1、状态转移方程:

  • 不放物品i:由dpi - 1j推出,即背包容量为j,里面不放物品i的最大价值,此时dpij就是dpi - 1j。(其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以背包内的价值依然和前面相同。)
  • 放物品i:由dpi - 1j - weight\[i]推出,dpi - 1j - weight\[i] 为背包容量为j - weighti的时候不放物品i的最大价值,那么dpi - 1j - weight\[i] + valuei (物品i的价值),就是背包放物品i得到的最大价值

所以递归公式: dpij = max(dpi - 1j, dpi - 1j - weight\[i] + valuei);

2、初始化

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

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

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

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

当j >= weight0时,dp0j 应该是value0,因为背包容量放足够放编号0物品。

java 复制代码
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)其他下标初始化:

dpij = max(dpi - 1j, dpi - 1j - weight\[i] + valuei)

dp11 = max(dp01, dp01- weight\[i] + valuei) 一定在其左方且上方,

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

二、一维(滚动数组)

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

java 复制代码
dp[j] = max(dp[j], dp[j - weight[i]] + value[i])

2、初始化

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

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

3、遍历顺序

倒序遍历

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

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

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

三、应用

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

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

示例 1:

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

分析:

物品是i,

重量是numsi,价值也是numsi,背包体积是sum/2。

类似于01背包

状态转移方程:

java 复制代码
dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])
java 复制代码
// 开始 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

分析:

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

那么分成两堆石头,一堆石头的总重量是dptarget,另一堆就是sum - dptarget

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

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

java 复制代码
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];

引自:代码随想录

相关推荐
小林ixn19 分钟前
LeetCode 206. 反转链表(迭代 + 递归详解)
算法·leetcode·链表
菜鸟‍2 小时前
LeetCode 1 27 和 704 || 两数之和 移除元素 二分查找
算法·leetcode·职场和发展
退休倒计时3 小时前
【每日一题】LeetCode 142. 环形链表 II TypeScript
算法·leetcode·链表·typescript
-森屿安年-6 小时前
91. 解码方法
c++·动态规划
sjsjs117 小时前
力扣3558. 给边赋权值的方案数 I
算法·leetcode·职场和发展
花间相见7 小时前
【LeetCode01】—— 无重复字符的最长子串:滑动窗口经典题详解
python·算法·leetcode
wabs6667 小时前
关于动态规划【力扣96.不同的二叉搜索树的递推公式怎么理解?】
算法·动态规划
言存9 小时前
力扣热题283 移动零
数据结构·算法·leetcode
珊瑚里的鱼9 小时前
【动态规划】买卖股票的最佳时机Ⅲ
算法·动态规划
渡之11 小时前
GeoBridge 深度解析:语义锚定多视图基础模型,重塑无人机跨视角地理定位
深度学习·算法·动态规划·无人机