leetcode-动态规划-01背包

一、二维数组

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物品。

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)其他下标初始化:

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]

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

类似于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

分析:

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

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

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

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

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];

引自:代码随想录

相关推荐
CV工程师小林5 小时前
【算法】BFS 系列之边权为 1 的最短路问题
数据结构·c++·算法·leetcode·宽度优先
天玑y6 小时前
算法设计与分析(背包问题
c++·经验分享·笔记·学习·算法·leetcode·蓝桥杯
sjsjs116 小时前
【数据结构-一维差分】力扣1893. 检查是否区域内所有整数都被覆盖
数据结构·算法·leetcode
m0_571957586 小时前
Java | Leetcode Java题解之第406题根据身高重建队列
java·leetcode·题解
山脚ice7 小时前
【Hot100】LeetCode—72. 编辑距离
算法·leetcode
鱼跃鹰飞8 小时前
Leetcode面试经典150题-349.两个数组的交集
算法·leetcode·面试
大二转专业9 小时前
408算法题leetcode--第七天
考研·算法·leetcode
戊子仲秋11 小时前
【LeetCode】每日一题 2024_9_19 最长的字母序连续子字符串的长度(字符串,双指针)
算法·leetcode·职场和发展
程序猿练习生14 小时前
C++速通LeetCode中等第5题-无重复字符的最长字串
开发语言·c++·leetcode
MogulNemenis17 小时前
力扣150题——位运算
数据结构·算法·leetcode