发布时间:2025-06-24 18:06:55  作者:北方职教升学中心  阅读量:379



状态转移方程类似第一问,但多了一个限制条件,即只有在前 i - 1 个物品、

🏳️‍🌈四、比如在一些动态规划问题中,如果状态转移只依赖于相邻的几个状态,就可以通过巧妙地复用数组空间来进一步减少空间的占用,不过这需要根据具体问题的特点来合理设计和实现。背包容量为j - v[i]时的最大价值基础上,加上第i个物品的价值w[i]dp[i][j]=max(dp[i][j],dp[i -1][j -v[i]]+w[i]);}}// 完成动态规划计算后,输出第一问的结果,即在前n个物品、
  1. 最终版代码(空间优化)
    我们可以发现,在计算 dp[i][j]时,只用到了 dp[i - 1][...]的值,所以可以将二维数组压缩成一维数组来优化空间复杂度。

    第二问解题思路:
    重新定义 dp[i][j]表示在前 i 个物品中,背包容量恰好为 j 时能装下的最大价值。dp[j]表示在容量为 j的情况下(类比背包容量),所能装入的石头重量的最大值(即从石头中挑选部分石头组成的最大重量和)。背包容量为V时,如果得到的结果是 -1,表示这种情况下无法恰好装满背包,按照要求输出0if(dp[n][V]==-1)cout <<0;// 如果不是 -1,说明能恰好装满背包,输出此时的最大价值elsecout <<dp[n][V];}// 64 位输出请用 printf("%lld")

    🧡416. 分割等和子集

    在这里插入图片描述
    整体思路:
    本题将能否分割数组成两个和相等子集的问题转化为一个 0/1 背包问题的变形来解决,通过动态规划的方式记录状态并进行状态转移,判断是否能从给定数组中选出一些元素,使其和等于数组总和的一半。01 背包问题概述

  2. 🏳️‍🌈二、
    然后,定义动态规划的状态表示,一般会使用dp[i][j],它的含义是将前 i 件物品放入容量为 j 的背包里所能获得的最大价值。问题分析与解法
    • ❤️(一)表示状态
    • 🧡(二)状态转移方程
    • 🧡(三)代码实现
  3. 🏳️‍🌈三、

    🧡(二)状态转移方程

    对于dp[i][j]这个状态,需要考虑第 i 件物品的选择情况,主要分为两种:
    不选第i件物品:此时背包里的最大价值就等于前 i - 1 件物品放入容量为 j 的背包里的最大价值,即 dp[i][j] = dp[i - 1][j]。如果总重量能被 2 整除且正好能平均分成两堆,那么结果就是 0;否则就得到了最后剩下那块石头的最小可能重量。关键在于将问题转化为求在一定容量限制下能装入的最大重量问题。

    🏳️‍🌈三、

    具体情况可以分为两种,即不选择 i位置的物品,结果为 f[i - 1][j]
    以及选择 i位置的物品,结果为 f[i][j - v[i]] + w[i],当然这是有前提条件的,即当前背包容量的最大值比这个物品的体积大,不然会越界

    在这里插入图片描述

    🏳️‍🌈二、

    💚(四)空间优化

    除了前面提到的将二维数组压缩成一维数组这种常见的空间优化方式外,还可以进一步采用滚动数组等技巧来优化空间。
    步骤详解:

    • 计算数组总和并判断可行性:
      首先遍历输入的数组 nums,计算出所有元素的总和sum。以下是示例代码:

      #include<iostream>#include<vector>#include<unordered_map>usingnamespacestd;unordered_map<int,unordered_map<int,int>>memo;// 记忆化搜索计算最大价值intmemoizedSearch(vector<int>&weight,vector<int>&value,intindex,intcapacity){if(index ==0||capacity ==0){return0;}if(memo.count(index)&&memo[index].count(capacity)){returnmemo[index][capacity];}intres =memoizedSearch(weight,value,index -1,capacity);if(weight[index -1]<=capacity){res =max(res,memoizedSearch(weight,value,index -1,capacity -weight[index -1])+value[index -1]);}memo[index][capacity]=res;returnres;}intmain(){vector<int>weight ={2,3,4};vector<int>value ={3,4,5};intcapacity =5;cout <<"通过记忆化搜索背包能装的最大价值为: "<<memoizedSearch(weight,value,weight.size(),capacity)<<endl;return0;}

      通过使用 unordered_map 来作为记忆化的数据结构,记录不同 index(物品索引)和 capacity(背包容量)组合下的最大价值,大大减少了重复计算,提高了效率,但相较于动态规划,代码结构上还是相对复杂些,并且空间复杂度也会因为记忆化数据结构的使用有所增加。

    • 动态规划状态转移过程:
      外层循环使用变量i 从 0 到 n - 1(n 为石头数组 stones 的元素个数),依次遍历每一块石头,考虑将每块石头放入 “背包” 的情况。
    • 在每次循环中,先默认不选当前物品,然后判断如果背包容量够放当前物品,就比较放和不放当前物品哪种情况能得到更大价值,更新dp[i][j]的值。

      🧡(二)记忆化搜索

      为了避免暴力搜索中大量的重复计算问题,可以采用记忆化搜索的方式。
      初始化时,对于没有物品可选但背包有容量的情况标记为不可能装满(设为 -1)。

      📢博客主页:https://blog.csdn.net/2301_779549673
      📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
      📢本文由 JohnKi原创,首发于 CSDN🙉
      📢未来很长,值得我们全力奔赴更美好的生活✨

      在这里插入图片描述

      在这里插入图片描述

      文章目录

      • 🏳️‍🌈一、
        在每次内层循环中,对于dp[j]的更新,需要比较两种情况:一是不选当前这块石头,那么 dp[j]的值保持之前的状态不变;二是选当前这块石头,此时它的值应该是在前容量为j - stones[i]时的最大重量 dp[j - stones[i]] 的基础上,再加上当前石头的重量 stones[i],取这两种情况的最大值来更新 dp[j]的值,即 dp[j] = max(dp[j], dp[j - stones[i]] + stones[i])
        若总和是偶数,则将其除以2,得到目标和,后续的操作就是围绕能否凑出这个目标和来进行判断。以下是简单的代码示例思路(实际完整代码可自行完善细节):

        #include<iostream>#include<vector>usingnamespacestd;// 递归计算选择当前物品后的最大价值intbruteForce(vector<int>&weight,vector<int>&value,intindex,intcapacity){if(index ==0||capacity ==0){return0;}intres =bruteForce(weight,value,index -1,capacity);if(weight[index -1]<=capacity){res =max(res,bruteForce(weight,value,index -1,capacity -weight[index -1])+value[index -1]);}returnres;}intmain(){vector<int>weight ={2,3,4};vector<int>value ={3,4,5};intcapacity =5;cout <<"通过暴力搜索背包能装的最大价值为: "<<bruteForce(weight,value,weight.size(),capacity)<<endl;return0;}

        这种方法虽然简单直接,但是效率非常低,因为它会有大量的重复计算,随着物品数量和背包容量的增加,时间复杂度会呈指数级增长。
        如果总和sum是奇数,那无论怎么划分都不可能得到两个和相等的子集,直接返回 false。01 背包问题概述

在这里插入图片描述

01 背包问题是一个非常经典的动态规划问题,其场景设定为:给定一个背包,它有一定的容量限制,同时有若干种物品,每种物品都有对应的重量和价值,且每种物品只能选择放入背包一次(即选择 0 个或者 1 个),目标是在满足背包容量限制的条件下,求出能够装入背包的物品的最大价值总和。背包容量为V时能装下的最大价值cout <<dp[n][V]<<endl;// 以下是求解第二问的动态规划过程// 先将整个dp数组初始化为0,用于重新计算第二问的情况memset(dp,0,sizeofdp);// 对于容量大于0且没有物品可选(即前0个物品)的情况,将其价值设为 -1,表示这种情况不可能恰好装满背包,是一种特殊的标记for(intj =1;j <=V;++j)dp[0][j]=-1;// 外层循环同样遍历每个物品,从第1个物品开始考虑for(inti =1;i <=n;++i){// 内层循环遍历不同的背包容量情况,从容量为1到最大容量Vfor(intj =1;j <=V;++j){// 先默认不选第i个物品时的情况,和第一问类似,此时最大价值等于前i - 1个物品在容量为j时的最大价值dp[i][j]=dp[i -1][j];// 判断背包当前容量j是否能容纳第i个物品,并且在前i - 1个物品、

  • 计算最后剩下石头的最小重量:
    经过上述动态规划过程,dp[t]就表示在容量为 t 的情况下能装入的最大重量。intdp[N][N];intmain(){// 输入物品个数和背包的最大体积cin >>n >>V;// 循环读取每个物品的体积和价值,注意这里索引从1开始,符合常规的计数习惯(第1个物品、

    #define_CRT_SECURE_NO_WARNINGS1#include<iostream>#include<vector>#include<stdio.h>#include<string.h>usingnamespacestd;// 定义一个较大的常量,用于表示数组的最大长度,这里假设最多有1010个物品或背包容量最大为1010等情况constintN =1010;intv[N],w[N];// 分别用于存储每个物品的体积和价值的数组,索引对应物品编号(从1开始)intn,V;// n表示物品的个数,V表示背包能容纳的最大体积// dp数组用于存储动态规划过程中的中间结果,dp[i][j]有两种含义:// 1. 对于第一问:表示在前i个物品中,背包容量不超过j时能装下的最大价值。01背包例题

    ❤️DP42 【模板】完全背包

    在这里插入图片描述
    整体思路:
    本题通过动态规划的方法解决背包问题,针对两个不同的问题要求分别进行了两次动态规划计算。01背包例题

    • ❤️[DP42 【模板】完全背包](https://www.nowcoder.com/practice/fd55637d3f24484e96dad9e992d3f62e?tpId=230&tqId=2032484&ru=/exam/oj&qru=/ta/dynamic-programming/question-ranking&sourceUrl=%2Fexam%2Foj%3Fpage%3D1%26tab%3D%25E7%25AE%2597%25E6%25B3%2595%25E7%25AF%2587%26topicId%3D196)
    • 🧡[416. 分割等和子集](https://leetcode.cn/problems/partition-equal-subset-sum/)
    • 💛[1049. 最后一块石头的重量 II](https://leetcode.cn/problems/last-stone-weight-ii/)
  • 👥总结

  • 🏳️‍🌈一、
    利用 dp数组存储中间结果,通过状态转移方程逐步推导出在不同物品数量和背包容量情况下的最大价值。

    💛(三)动态规划

    前面介绍的用二维数组或者优化后的一维数组实现的其实就是标准的动态规划解法。

    选择第 i件物品:前提是背包的容量 j 要大于等于第 i 件物品的重量 weight[i],那么此时背包里的最大价值就是前 i - 1 件物品放入容量为 j - weight[i] 的背包里的最大价值,再加上第 i 件物品本身的价值 value[i],即 dp[i][j] = dp[i - 1][j - weight[i]] + value[i](前提满足容量条件)。背包容量为 j - v[i] 时的状态是可以恰好装满背包(不是 -1 标记的情况)时,才考虑选择第 i 个物品来更新 dp[i][j] 的值,同样通过两层循环填充 dp 数组,最后根据 dp[n][V] 的值判断是否能恰好装满背包并输出相应结果。// 2. 对于第二问:表示在前i个物品中,背包容量恰好为j时能装下的最大价值。

  • 初始化动态规划数组 dp:
    创建一维整数数组 dp,长度为t + 1,并将所有元素初始化为 0。由于我们希望两堆石头重量尽可能接近总重量的一半,所以最后剩下的石头重量可以通过总重量 sum 减去两倍的 dp[t]来得到。第2个物品等)for(inti =1;i <=n;++i)scanf("%d %d",&v[i],&w[i]);// 以下是求解第一问的动态规划过程// 外层循环遍历每个物品,从第1个物品开始,逐步考虑到所有的n个物品for(inti =1;i <=n;++i++){// 内层循环遍历背包的不同容量情况,从容量为1开始,到最大容量Vfor(intj =1;j <=V;++j){// 初始化dp[i][j],先默认不选第i个物品时的情况,此时最大价值等于前i - 1个物品在容量为j时的最大价值dp[i][j]=dp[i -1][j];// 判断背包当前容量j是否能够容纳第i个物品(即体积是否够放)if(j >=v[i])// 如果能放,就需要比较放和不放第i个物品哪种情况能让背包中物品的价值更大// 不放就是dp[i - 1][j],放的话就是在前i - 1个物品、
  • 动态规划状态转移过程:
    通过两层嵌套循环来填充 dp 数组,外层循环变量 i 从 1 到 n(n 是数组 nums 的元素个数),表示逐步考虑数组中的每个元素。
    然后将总重量sum除以 2 得到 t,这里把问题抽象成一个类似背包容量的概念,目标是从这些石头中选取一些石头,使其重量之和尽量接近 t,这样就能让两堆石头的重量尽可能均衡,最终剩下的石头重量最小。首先,用两个数组分别来表示物品的重量和价值,例如weight[n]表示 n 个物品各自的重量,value[n]表示 n 个物品各自的价值(这里 n 为物品的总数量)。
    通过两层循环,外层遍历物品,内层遍历背包容量,逐步填充 dp 数组,最终得到第一问的答案 dp[n][V]。
  • 最后返回将所有物品考虑完后,给定背包容量下能得到的最大价值
    然后判断当前目标和 j 是否大于等于第 i 个元素的值(因为索引的对应关系,实际判断 j >= nums[i - 1]),并且在前 i - 1 个元素中已经能够凑出和为j - nums[i - 1]的情况(即 dp[i - 1][j - nums[i - 1]]true),如果满足这两个条件,说明选了第 i 个元素后能凑出和为 j,则将 dp[i][j]更新为 true。其核心思路是创建一个记忆化数组,用来记录已经计算过的子问题的解,下次再遇到同样的子问题时,直接从记忆化数组中获取结果,而不用重新计算。动态规划的核心就是通过记录子问题的解,避免重复计算,利用状态转移方程逐步构建出整个问题的最优解。多种实现方式与优化
    • ❤️(一)暴力搜索
    • 🧡(二)记忆化搜索
    • 💛(三)动态规划
    • 💚(四)空间优化
  • 🏳️‍🌈四、
    对于没有元素可选(i = 0)但目标和 j 大于 0 的情况,显然是无法凑出目标和的,所以将 dp[0][j](j > 0)设为 false,而 dp[0][0]设为 true,表示和为 0 不需要任何元素就可以达到,这是边界情况的设定。以下是优化后的代码示例:
  • #include<iostream>#include<vector>usingnamespacestd;intknapsack(vector<int>&weight,vector<int>&value,intcapacity){intn =weight.size();vector<int>dp(capacity +1,0);for(inti =0;i <n;++i){// 注意这里要倒序遍历背包容量,防止同一个物品被多次放入背包for(intj =capacity;j >=weight[i];--j){dp[j]=max(dp[j],dp[j -weight[i]]+value[i]);}}returndp[capacity];}intmain(){vector<int>weight ={2,3,4};vector<int>value ={3,4,5};intcapacity =5;cout <<"背包能装的最大价值为: "<<knapsack(weight,value,capacity)<<endl;return0;}

    这里关键在于内层循环倒序遍历背包容量,这样就能保证每个物品只会被考虑放入背包一次,利用了之前已经计算好的状态,同时节省了空间,只用了一个一维数组 dp 来记录不同背包容量下的最大价值情况,最后返回对应背包容量下的最大价值即可。

  • 初始化动态规划数组 dp:
    创建二维布尔类型的 dp数组,dp[i][j]的含义是在数组 nums的前 i 个元素中(实际对应 nums[0]nums[i - 1]),是否存在一些元素的和等于 j。
    具体步骤:

    • 计算石头总重量并确定目标容量:
      首先通过循环遍历输入的石头重量数组 stones,累加计算出所有石头的总重量 sum

      第一问解题思路:
      定义 dp[i][j]表示在前 i个物品中,背包容量不超过 j 时能装下的最大价值。倒序遍历是非常关键的一点,它保证了在计算 dp[j] 时,每个石头只会被考虑放入 “背包” 一次,避免了重复选取同一块石头的错误情况。// 2. 选当前这块石头,此时需要在前容量为j - stones[i]的最大重量基础上,加上当前石头的重量stones[i],即dp[j - stones[i]] + stones[i]。dp[j]=max(dp[j],dp[j -stones[i]]+stones[i]);}}// 根据前面的思路,将所有石头分成两堆,使得两堆重量尽可能接近总重量的一半t,那么最后剩下的石头重量就是总重量sum减去两倍的能装入容量为t的“背包”的最大重量dp[t]returnsum -2*dp[t];}};


      👥总结


      本篇博文对 动态规划中01背包问题解析做了一个较为详细的介绍,不知道对你有没有帮助呢

      觉得博主写得还不错的三连支持下吧!会继续努力的~

      请添加图片描述