代码随想录算法训练营第30天|动态规划:01背包理论基础、卡玛网46、动态规划:01背包理论基础(滚动数组)、416. 分割等和子集
动态规划:01背包理论基础
2025-03-03 19:43:53 星期一
文档讲解:代码随想录(programmercarl)01背包理论基础
视频讲解:《代码随想录》算法视频公开课 (opens new window):带你学透0-1背包问题!
代码随想录视频内容简记
首先是01背包的问题描述:

给定n种物品,每种物品各一个。每种物品都有自己的重量和价值,如果有一个背包只能放重量为m的物品,那么问:放的物品最多的价值是多少?
梳理
-
dp[i][j]数组的含义:这里,用i来表示物体,j来表示背包容量(并不是题目实际给出的背包容量,而是从0开始遍历得到的数)。从下标为[0-i]的物品里任意取,注意这里是从0到i,这里这个下标具有特殊的含义,是理解后面递推公式的核心,放进容量为j的背包,那么背包中的物品价值总和最大是dp[i][j]。
-
确定递推公式,这里举个例子好理解。比如
dp[1][4]
,那么在背包中存放的物品就是只有0和1两种,而不是只有1没有0。那么dp[1][4]
的含义就是从下标为[0-1]
的物品里任意取,放进容量为4的背包,那么得到的物品价值总和最大是dp[1][4]
。接着,需要分两种情况,第一种是背包中不放物品1,第二种是背包中放物品1
-
不放物品1。那么就是只从下标为
i - 1
也就是0取就可以了。所以是dp[i - 1][j]
-
放物品1。这里需要想明白,就是如果是dp[1][4],那么背包的容量是4,物品1的重量是3,放物品1之前背包中的容量就只是1了,那么放物品1之前,放其他的物品的重量的限制就是不能超过1。
所以,我们需要用
dp[i - 1][j - weight[i]] + value[i]
确定了两种情况,那么最后就是需要取最大值了,d
p[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])
其实可以看到,这里的dp值是从上方和左上方(不一定是正好挨着的左上角)
-
-
明确了递推公式,那么接下来就是初始化dp数组。在背包问题的dp[i][j]初始化中,没有固定的形式,具体要根据物体的容量和价值来进行初始化
首先是背包能放的重量如果是0,那么所有的物品的dp[i][j]都是0,也就是第一列一定是0
然后是针对于物品0在不同的背包重量的承受范围内进行添加,如果背包的承受重量为1,刚好可以放物品0,那么dp[0][1]就是15,后面依次都是15。这里如果背包1的重量是2,那么dp[0][1]就是0了
-
确定遍历顺序,这个是或者先遍历背包,或者先遍历物品,两种都可以。都不影响
-
打印dp数组
卡玛网46
题目描述:卡玛网46. 携带研究材料
注意1
这两行代码记得进行初始化为0,否则后面需要单独对左边列和最上面行进行初始化
vector<int> weight(n, 0);
vector<int> value(n, 0);
这个题昨天写的时候一直报存在潜在的指针越界或异常,今天重新听了一遍k哥的视频梳理了一下思路,然后小问题改了一下直接AC了
注意2
if (j < weight[i]) dp[i][j] = dp[i - 1][j];
另外就是这行代码在遍历中的作用就是如果背包此时的容量不能放下物品,那么就及时继承上面的dp[i - 1][j]
卡码网测试
点击查看代码
# include<iostream>
# include<vector>
using namespace std;
int main() {
int n, bagweight;
cin >> n >> bagweight;
vector<int> weight(n, 0);
vector<int> value(n, 0);
vector<vector<int>> dp(n, vector<int>(bagweight + 1, 0));
for (int i = 0; i < n; i++) {
cin >> weight[i];
}
for (int i = 0; i < n; i++) {
cin >> value[i];
}
//dp数组初始化
for (int i = 1; i <= bagweight; i++) {
if (i >= weight[0]) dp[0][i] = value[0];
}
// 先遍历背包,再遍历物品
for (int i = 1; i < n; i++) {
for (int j = 1; j <= bagweight; j++) {
if (j < weight[i]) dp[i][j] = dp[i - 1][j];
else {
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
}
}
}
cout << dp[n - 1][bagweight];
return 0;
}
动态规划:01背包理论基础(滚动数组)
文档讲解:代码随想录(programmercarl)01背包理论基础(滚动数组)
视频讲解:《代码随想录》算法视频公开课:带你学透0-1背包问题!(滚动数组)
代码随想录视频内容简记
这里是第二种方式,就是用一维dp数组进行实现,和上面的二维dp数组进行区分,其核心就是把二维这么一个大表格的数据进行了压缩,压缩到每一行,然后每次覆盖进行重写。
需要注意的是,这里虽然是dp数组变成一维的了,但是遍历时候仍然是两层for循环
梳理
-
确定dp[j]数组的定义,表示容量为j的背包所能放物品的最大价值是dp[j]
-
确定递推公式,
dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
-
dp数组初始化,初始化时对于下表为0的dp,只需要对
dp[j] = 0
即可。而对于下标不为0的dp,需要初始化为一个非零的最小数,也就是0,因为大了的话递推公式就不起作用了 -
确定遍历顺序,注意这里有两点:一是需要先遍历物品,后遍历重量;二是对重量的遍历需要后序,不能是前序。
如果是前序,那么每次取max的时候都会进行迭代一次,把前面算出来的一个dp[j]作为dp[j - weight[i]]又加上value[i],那么就是随着背包容量的增大,物品不止被添加了一次,所以添加重了。如果是后序遍历的话,前面的是0,就可以有效防止添加重了
为什么不能先遍历重量?这里大致的意思就是如果先遍历重量,那么每次取max得到的就是一个物品
-
打印dp数组
卡玛网测试
整体思路不变,主要就是在循环主体种注意这里的判断
if (j < weight[i]) dp[j] = dp[j];
else dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
因为dp数组是不断覆盖重写的,所以如果不比上一个dp数组大的化,那么就不变
点击查看代码
# include<iostream>
# include<vector>
using namespace std;
int main () {
int n, bagweight;
cin >> n >> bagweight;
vector<int> weight(n, 0);
vector<int> value(n, 0);
for (int i = 0; i < n; i++) {
cin >> weight[i];
}
for (int i = 0; i < n; i++) {
cin >> value[i];
}
// 定义dp数组
vector<int> dp(bagweight + 1, 0);
// 遍历
for (int i = 0; i < n; i++) {
for (int j = bagweight; j >= 0; j--) {
if (j < weight[i]) dp[j] = dp[j];
else dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
cout << dp[bagweight];
return 0;
}
LeetCode416
题目描述:力扣416
文档讲解:代码随想录(programmercarl)416. 分割等和子集
视频讲解:《代码随想录》算法视频公开课:动态规划之背包问题,这个包能装满吗?| LeetCode:416.分割等和子集
代码随想录视频内容简记
本题k哥视频用的是一维的dp[j]数组,还有就是本题的巧妙之处就是题目抽象成的01背包问题,他的物品价值和重量都是同一个数组
本题抽象成,能不能将一个重量和价值都一样的n个物品,装到一个背包种使其满足价值为target,重量也为target
梳理
-
确定dp[j]数组的含义,表示的是容量为j的背包所能放物品的最大价值为dp[j]
-
确定递推公式,
dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
-
初始化dp数组,在定义dp的时候直接先都初始化为0,就是注意
dp[bagweight] = 0
需要为0 -
确定遍历顺序,依旧是先遍历物品,后便利背包,从后向前
-
打印dp数组
LeetCode测试
这里和之前的是一样的,就是在题目中说明了
给你一个 只包含正整数 的 非空 数组 nums
所以在for循环遍历j的时候可以去直接>=1
,改成0也没问题,没有影响,因为定义dp的时候就多给了一位。
点击查看代码
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = 0;
for (int i = 0; i < nums.size(); i++) {
sum += nums[i];
}
if (sum % 2 == 1) return false;
int target = sum / 2;
// cout << target << endl;
vector<int> dp(target + 1, 0);
for (int i = 0; i < nums.size(); i++) {
for (int j = target; j >= 1; j--) {
if (j < nums[i]) dp[j] = dp[j];
else dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
}
}
// cout << dp[target];
if (dp[target] == target) return true;
return false;
}
};
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端