代码随想录动态规划3(背包问题)

1背包问题 (一维)0-1背包问题,每个物品只能被选择一次

代码随想录

视频讲解:

递推公式:

最终,我们希望的 dp[j] 是这两种方案的最大值

dp[j]=max⁡(dp[j],dp[j−weight[i]]+value[i])dp[j] = \max(dp[j], dp[j - weight[i]] + value[i])dp[j]=max(dp[j],dp[j−weight[i]]+value[i])

这个公式的意思是:

  • dp[j]:当前的行李箱容量是 j 时,它的最优价值是多少?
  • dp[j - weight[i]]:如果选了当前材料,它占用了 weight[i] 的空间,所以剩下的空间是 j - weight[i],而这个空间的最优价值已经计算过了。
  • dp[j - weight[i]] + value[i]:意味着加上当前材料的价值,看看总价值有没有提升。
  • 遍历顺序为逆序遍历:
  •  
    #include<iostream>
    #include<vector>
    using namespace std;
    int main()
    {
        int a,b;
        cin>>a>>b;
        vector<int>weight(a);
        vector<int>value(a);//首先需要定义数组
        vector<int>dp(b+1);
        for(int i=0;i<a;i++)cin>>weight[i];
        for(int j=0;j<a;j++)cin>>value[j];
        for(int i=0;i<a;i++)//遍历每一种物品,每一轮判断是否装入该物品,并不断更新dp[i]
        {
            for(int j=b;j>=weight[i];j--)//逆序遍历,0-1背包问题避免一个物体在同一轮中被重复选择
            {
                dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);//递推公式
            }
    
        }
        cout<<dp[b];
    }

    举例子来验证会更清晰

  • 输入数据

    材料体积(kg)价值($)
    A(实验设备)210
    B(实验文献)320
    C(实验样本)430
    行李箱容量: N = 5

    我们使用 dp[j] 数组,dp[j] 表示容量为 j 的行李箱所能装下的最大价值

    初始状态:

    dp[0] dp[1] dp[2] dp[3] dp[4] dp[5] [ 0 0 0 0 0 0 ]


    正序遍历的错误

    正序遍历的代码(有问题):

    for (int i = 0; i < M; i++) { // 遍历每种材料 for (int j = weight[i]; j <= N; j++) { // ⚠️ 正序遍历 dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); } }

    我们按照正序 j 计算,看看发生了什么问题。


    Step 1:处理材料 A(体积 2,价值 10)

  • j = 2,更新 dp[2] = max(dp[2], dp[0] + 10) = max(0, 10) = 10
  • j = 3,更新 dp[3] = max(dp[3], dp[1] + 10) = max(0, 10) = 10
  • dp 数组:

  • j = 4,更新 dp[4] = max(dp[4], dp[2] + 10) = max(0, 10 + 10) = 20错误
  • j = 5,更新 dp[5] = max(dp[5], dp[3] + 10) = max(0, 10 + 10) = 20错误
  • 原因:在同一轮中重复使用了dp[2],dp[3],导致材料A不止被选择一次,因为第一遍计算dp[2]表示该物体已经被选择一次,第2次计算dp[4]使用已经更新过的dp[2]计算,表示又被选择了一次。max的过程实质就是一个斟酌选不选材料A的过程

416. 分割等和子集

本题是 01背包的应用类题目

代码随想录

视频讲解:动态规划之背包问题,这个包能装满吗?| LeetCode:416.分割等和子集_哔哩哔哩_bilibili

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

如何转化为背包问题

  1. 计算数组的总和:首先,计算数组的总和 sum。如果 sum 是奇数,那么无法将数组分割成两个和相等的子集,因为如果能够分割,两个子集的和必须相等,而每个子集的和必然是 sum/2。所以如果 sum 为奇数,直接返回 false

  2. 目标和为 sum / 2:假设 sum 为偶数,那么我们需要检查是否能够从数组中找到一个子集,其和为 sum / 2。如果存在这样的子集,那么剩余的元素和必然也为 sum / 2,从而实现分割。

  3. 动态规划的背包思路:我们使用动态规划来判断是否能够找到一个子集,其和为 sum / 2。将问题转化为一个背包问题,背包的容量是 sum / 2,每个物品的重量就是数组中的一个元素。我们需要判断是否能填充背包,使得背包的总重量正好为 sum / 2

  4. 动态规划解决方案

  5. dp数组定义:定义一个布尔类型的数组 dp,其中 dp[i] 表示是否可以从数组中选出若干元素,得到和为 i 的子集。

  6. 初始化:初始化 dp[0] = true,因为和为 0 总是可以的,即不选择任何元素。

  7. 状态转移:遍历数组中的每个元素,更新 dp 数组。对于每个元素 num,从背包容量 target = sum / 2 开始,逆序遍历 dp 数组,检查是否可以通过选取该元素来更新 dp 数组的状态。即,如果 dp[j - num]true,则 dp[j] 也应该为 true

  8. 结果:如果 dp[target]true,表示存在一个和为 target 的子集,返回 true;否则,返回 false

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum=0;
        for(int nums:nums)
        {
        sum+=nums;
        }
        if(sum%2)return false;
        int target=sum/2;
        vector<bool>dp(target+1,false);//明确dp[n]的含义,表示数组中是否能找到和为n的子集
        dp[0]=true;//初始化,表示和为0可以通过不选任何元素得到,是成立的
        for(int nums:nums)//表示遍历背包中的每一个物品,判断是否添加
        {
            for(int j=target;j>=nums;j--)//逆序遍历,不能一步到位求dp[target],需要数组每个数的积累
            {
                dp[j]=dp[j]||dp[j-nums];//表示两种可能性,保持原状不添加数字,或者添加该遍历到的数字
            }
        }
        return dp[target];

    }
};

思考:其实dp[n]数组的含义是非常直观的,可以直接从题目中获取,请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。这句话就表明返回类型为布尔类型,true or false

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值