01背包问题

背包问题:

有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weighti,得到的价值是valuei 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

我们需要使用二维数组,为什么呢?

因为有两个维度需要分别表示:物品 和 背包容量

如图,二维数组为 dpij。

那么这里 i 、j、dpij 分别表示什么呢?

i 来表示物品、j表示背包容量。

背包容量为 0,放不下物品0 或者物品1,此时背包里的价值为0。

背包容量为 1,只能放下物品0,背包里的价值为15。

背包容量为 2,只能放下物品0,背包里的价值为15。

背包容量为 3,上一行同一状态,背包只能放物品0,这次也可以选择物品1了,背包可以放物品1 或者 物品0,物品1价值更大,背包里的价值为20。

背包容量为 4,上一行同一状态,背包只能放物品0,这次也可以选择物品1了,背包可以放下物品0 和 物品1,背包价值为35。

dp14 表示什么意思呢。

任取 物品0,物品1 放进容量为4的背包里,最大价值是 dp14。

通过这个举例,我们来进一步明确dp数组的含义。

即dpij 表示从下标为0-i的物品里任意取,放进容量为j的背包,价值总和最大是多少。

两种情况,分别是放物品1 和 不放物品1,我们要取最大值(毕竟求的是最大价值)

dp[1][4] = max(dp[0][4], dp[0][1] + 物品1 的价值)

以上过程,抽象化如下:

  • 不放物品i:背包容量为j,里面不放物品i的最大价值是dpi - 1j。

  • 放物品i:背包空出物品i的容量后,背包容量为j - weighti,dpi - 1j - weight\[i] 为背包容量为j - weighti且不放物品i的最大价值,那么dpi - 1j - weight\[i] + valuei (物品i的价值),就是背包放物品i得到的最大价值

递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

一维:

DeepSeek

dpj表示:容量为j的背包,所背的物品价值可以最大为dpj,那么dp0就应该是0,因为背包容量为0所背的物品的最大价值就是0。

那么dp数组除了下标0的位置,初始为0,其他下标应该初始化多少呢?

看一下递归公式:dpj = max(dpj, dpj - weight\[i] + valuei);

dp数组在推导的时候一定是取价值最大的数,如果题目给的价值都是正整数那么非0下标都初始化为0就可以了。

只能从后向前遍历,不然会出现物品0就会被重复加入多次

  • 第一步:算 j = 2

    公式:dp[2] = max(dp[2], dp[2 - 1] + 15) = max(0, dp[1] + 15)

    关键来了: 此时的 dp[1] 是多少?因为是倒序,dp[1] 还没有 被这一轮更新过,它依然保留着初始状态的 0 。

    所以:dp[2] = max(0, 0 + 15) = 15。

    ✅ 含义:用容量2的背包装物品0,价值是15(剩下的1容量空着)。

  • 第二步:算 j = 1

    公式:dp[1] = max(dp[1], dp[1 - 1] + 15) = max(0, dp[0] + 15)

    dp[0] 永远是0。

    所以:dp[1] = 15。

    ✅ 含义:用容量1的背包装物品0,价值是15。

最终结果: dp[2] = 15。这完全正确,因为自始至终只有 1 个物品。

场景设定(2个物品,背包容量为4)

物品 重量 价值
物品0 2 10
物品1 1 1

现实世界的正确答案 :容量为4,最佳方案是同时拿物品0 + 物品1 (总重量 2+1=3 ≤ 4),总价值 = 10 + 1 = 11。

处理物品0(重量2,价值10)------ 倒序

  • 算 j=4:dp[4] = max(0, dp[2]+10)。此时 dp[2] 还没被更新 (还是0),所以 dp[4]=10。

  • 算 j=3:dp[3] = max(0, dp[1]+10) = 10。

  • 算 j=2:dp[2] = max(0, dp[0]+10) = 10。

此时 dp 数组状态:[0, 0, 10, 10, 10]

✅ 关键:容量4只装了物品0一次,价值是10(剩下2容量空着,但物品0不能再用了)。

2. 处理物品1(重量1,价值1)------ 倒序

  • 算 j=4:dp[4] = max(现有10, dp[3]+1)。

    注意,dp[3] 还是上一轮(没选物品1时)的旧数据,等于 10 。

    所以 10 + 1 = 11,dp[4] 被更新为 11。

  • 算 j=3:dp[3] = max(现有10, dp[2]+1=11) = 11。

  • 算 j=2:dp[2] = max(10, dp[1]+1=1) = 10。

  • 算 j=1:dp[1] = 1。

最终答案:11。完全正确!物品0和物品1各拿了一次

目标和:

5步走:

1.dp【i】【j】表达的是用0-i的物品填满容量为j的书包有几种方法。

情况一:if (nums[i] > j) ------ 装不下

当前数字太大,超过了目标容量 j。

  • 决策 :别无选择,只能不选它。

  • 结果 :方法数完全继承上一行的结果,即 dp[i][j] = dp[i - 1][j]。

情况二:else ------ 装得下

当前数字小于等于目标容量 j。

  • 决策 :此时你有两种互斥的选择,总方法数等于这两种情况之和(分类加法计数原理)。

  • 选择1(不选) :无视当前数字,方法数为 dp[i - 1][j]。

  • 选择2(选) :决定把当前数字放进去。

    既然放进去占了 nums[i] 的空间,那么剩下的 j - nums[i] 容量,就必须由前 i-1 个数字 来凑。

    凑出剩余容量的方法数为 dp[i - 1][j - nums[i]]。

  • 结果 :dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]]。

3.初始化:

第一列dpi0,如果数字大于0,为0的都只有一种情况,就是不放。

第一行dp0j,如果这个j小于nums【0】,就只有一种方法就是不放,如果大于就只有零种方法。

如果nums【0】就是0呢?

如果有两个物品,物品0为0, 物品1为0,装满背包容量为0的方法有几种。

  • 放0件物品
  • 放物品0
  • 放物品1
  • 放物品0 和 物品1

此时是有4种方法。

其实就是算数组里有t个0,然后按照组合数量求,即 2^t 。

初始化如下:

复制代码
int numZero = 0;
for (int i = 0; i < nums.size(); i++) {
    if (nums[i] == 0) numZero++;
    dp[i][0] = (int) pow(2.0, numZero);
}
cpp 复制代码
class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum=0;
        for(int n:nums){
            sum+=n;
        }
        int left=(sum+target)/2;
        if((sum+target)%2==1)return 0;
        if(sum<abs(target))return 0;
        vector<vector<int>>dp(nums.size(),vector<int>(left+1,0));
        int numZero = 0;
         dp[0][0] = 1; 
          if (nums[0] <= left) dp[0][nums[0]] += 1;//为什莫是+=呢?因为在0的情况下,上面等于零时因为不放他已经是1了,在这里自己相等的数加一种放入自己的情况,就在加1。
        for (int i = 0; i < nums.size(); i++) {
         if (nums[i] == 0) numZero++;
         dp[i][0] = (int) pow(2.0, numZero);
        }
       // 初始化最上行
        //
        // 初始化最左列,最左列其他数值在递推公式中就完成了赋值
        
        for(int i=1;i<nums.size();i++){
            for(int j=0;j<=left;j++){
                if(nums[i]>j)dp[i][j]=dp[i-1][j];
              else  dp[i][j]=dp[i-1][j]+dp[i-1][j-nums[i]];
              cout<<dp[i][j]<<" ";
            }
        }

        return dp[nums.size()-1][left];

    }
};

一和零

开始动规五部曲:

  1. 确定dp数组(dp table)以及下标的含义

dpij:最多有i个0和j个1的strs的最大子集的大小为dpij。

2.确定递推表达式

dpij 可以由前一个strs里的字符串推导出来,strs里的字符串有zeroNum个0,oneNum个1。

dpij 就可以是 dpi - zeroNumj - oneNum + 1。

然后我们在遍历的过程中,取dpij的最大值。

所以递推公式:dpij = max(dpij, dpi - zeroNumj - oneNum + 1);

此时大家可以回想一下01背包的递推公式:dpj = max(dpj, dpj - weight\[i] + valuei);

相关推荐
hetao17338373 小时前
2026-10-03~04 hetao1733837 的刷题记录
c++·算法
Zootopia6264 小时前
飞行力学知识梳理1|飞行性能与稳定性
人工智能·python·算法·机器学习·无人机·学习方法·信息与通信
longlongzihan4 小时前
从LeetCode 70. 爬楼梯看动态规划:如何精准推导状态转移方程?
c++·算法·leetcode·动态规划
2601_960356385 小时前
2027校招采购岗解析:数学基础如何迁移到需求预测、成本与供应商分析
人工智能·算法
h_a_o777oah6 小时前
【树论】树链剖分:将树形结构转化为线性结构以处理树上路径问题(洛谷 P3384 附C++代码模板)
数据结构·算法·线段树·acm·树链剖分·树论·重链剖分
HEJOO96 小时前
指针的算术运算详解
java·数据结构·算法
Coder-magician6 小时前
《代码随想录》刷题打卡day47:并查集-part01
数据结构·算法·图论
垆边人似月.6 小时前
VLAN 资源池(100~200 分档)
数据结构·算法
潼心1412o7 小时前
C++初阶(长期更新)第6讲:类和对象(下)
开发语言·c++·算法
星光开发者7 小时前
基于决策树随机森林算法的小说推荐系统的设计与实现-计算机毕设【课程设计】78485
vue.js·算法·微信小程序·django·php·课程设计·express