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=4dp[4] = max(0, dp[2]+10)。此时 dp[2] 还没被更新 (还是0),所以 dp[4]=10

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

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

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

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

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

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

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

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

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

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

  • j=1dp[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);

相关推荐
普通攻击往后拉1 小时前
Leetcode 448. 找到所有数组中消失的数字
算法·leetcode·职场和发展
mifengxing1 小时前
LeetCode 189 轮转数组|3种解法拆解,从暴力到O(1)原地最优解
数据结构·算法·leetcode
MIngYaaa5201 小时前
2026暑期牛客多校3 2026-7-24
数据结构·算法·000
geats人山人海2 小时前
算法基础第二讲 基础算法下
算法
阳明山水2 小时前
销量预测2026下半年:新架构、新基准与新共识
人工智能·深度学习·算法·机器学习·架构
知识分享小能手3 小时前
高等数学学习教程,从入门到精通,定积分 — 知识点全面总结(9)
学习·算法·机器学习
noipp3 小时前
推荐题目:洛谷 P3726 [AHOI2017/HNOI2017] 抛硬币
c语言·数据结构·c++·算法·编程·洛谷·luogu
c238563 小时前
C/C++每日一练14
c语言·c++·算法
程序员-珍3 小时前
力扣 877. 石子游戏
算法·leetcode·游戏