动态规划算法经典问题——01背包问题

问题情景:给你一个背包,告诉你其最大体积V,然后有一系列物品,每一个物品都有其对应的价值和体积。每个物品只能装一次(只有一个)就会有两种问法:

1.在不超过背包体积的情况下(不一定非要装满)背包物品最大价值总和?

2.在恰好装满背包的情况下,包内最大价值和?也有可能无论怎么装都无法恰好装满,返回-1,0等一些标记值。

我们接下来推一下状态表示和转移方程

如果,dp[i]表示从前i个物品中选取不超过背包体积的最大价值,我们发现这个dp数组就和体积变量无关了。而我们每装一个物品其背包剩余体积都会变,因此一维数组无法满足我们的表示需求。

改进:dp[i][j]表示在不超过j体积的情况下从前i个物品中选取最大总价值。(对于问题1的状态表示)

而对于第二种问题,dp[i][j]表示从前i个物品中选恰好能凑出j个体积的最大总价值(也有可能组不出)

我们先推导问题1的状态表示方程:

到第i个物品时,我们也可以选择选或不选,然后在这两种情况取最大值即可。

当不选时,那和从前i-1个选法是一样的。

当选时,首先要判断第i个物品体积是否大于j,如果大于了,只选第i个都已经过大了,显然不满足要求,因此我们要加个判断条件,在此条件下,它就等于从前i-1个物品中选一个体积不超过j-v[i]的价值加第i个物品价值(并不是看前一个元素)。(v数组表示每个物品的体积,w数组表示每个物品的价值)

在数组初始化方面,我们采取多一行一列表示,这样数组下标就能和物品序号对应了(第1,第2个物品。。。。)

第一行我们初始化为0(第一行表示前0个物品找出不超过j的最大价值,都没有物品,价值肯定都是0)

第一列我们也是都初始化为0(选出一个不超过0的总价值,肯定什么都选不了)

最终结果我们取dp[n][V]即可(题意中的在n个物品中选体积不超过V背包的总价值)

接下来我们来看第二个问题:

状态转移方程与第一个几乎相同。

首先不一样的是,我们会遇到无论怎么装都装不满的情况,这就不需要谈最大价值了,这种情况我们统一赋值为-1(人为规定)。

而当不选第i个物品,那么和dp[i-1][j]结果是相同的

如果选第i个,除了考虑是否v[i]<=j,还要满足dp[i-1][j-v[i]]不等于-1(即满足要求恰好装满)这时候背包恰好剩下v[i]个位置直接放入即可。

依旧是取二者情况最大值。

初始化方面,第一行除了第一个位置我们为0剩下都是-1,因为我选第0个物品的情况下(即不选)我是无法把你的背包装满的,但是第一个位置背包体积为0,某种意义上也满足要求,但价值是0.

第一列我们初始化为0.

接下来我们写一下二种问题的模板:

cpp 复制代码
//假设v[N],w[N]已经赋值
// int dp[N];
// int n ,V;
cin>>n>>v; 代表物品个数和背包体积


//第一种问题
for(int i=1;i<=n;i++)
  for(int j=1;j<=V;j++)
    {
      dp[i][j]=dp[i-1][j];
      if(j>=v[i]) dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);
    }

return dp[n][V];

//第二种问题

for(int j=1;j<=V;j++) dp[0][j]=-1;


for(int i=1;i<=n;i++)
  for(int j=1;j<=V;j++)
    {
      dp[i][j]=dp[i-1][j];
      if(j>=v[i]&&dp[i-1][j-v[i]]!=-1) dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);
    }

return dp[n][V]==-1?0:dp[n][V]

代码优化:滚动数组思想

我们也可以不用二维数组,用一维代替二维,即当填完第一行后,第二行根据第一行的数据进行赋值(把上一次的赋值覆盖,反正我们要的是最后一行的数据)

填表顺序就要从右向左填了,因为j-v[i]一定是在j的左边,如果我们从左往右可能就找不到上次的值了。

cpp 复制代码
//假设v[N],w[N]已经赋值
// int dp[N];
// int n ,V;
cin>>n>>v; 代表物品个数和背包体积


//第一种问题
for(int i=1;i<=n;i++)
  for(int j=V;j>=v[i];j--)
    {
      dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
    }

return dp[V];

//第二种问题

for(int j=1;j<=V;j++) dp[0][j]=-1;


for(int i=1;i<=n;i++)
  for(int j=V;j>=v[i];j--)
    {
      if(dp[j-v[i]]!=-1) dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
    }

return dp[V]==-1?0:dp[V]

关于内层for循环的判断条件的解释

如果其不满足条件,直接就是上一次的值(dp[i-1][j])映射了我们的二维时的思想。

相关推荐
R-G-B2 小时前
归并排序 (BM20 数组中的逆序对)
数据结构·算法·排序算法
少许极端2 小时前
算法奇妙屋(十二)-优先级队列(堆)
数据结构·算法·leetcode·优先级队列··图解算法
kupeThinkPoem3 小时前
哈希表有哪些算法?
数据结构·算法
小白程序员成长日记3 小时前
2025.11.16 力扣每日一题
算法
Kuo-Teng4 小时前
LeetCode 118: Pascal‘s Triangle
java·算法·leetcode·职场和发展·动态规划
Greedy Alg4 小时前
LeetCode 32. 最长有效括号(困难)
算法
ShineWinsu5 小时前
对于数据结构:链式二叉树的超详细保姆级解析—中
数据结构·c++·算法·面试·二叉树·校招·递归
野蛮人6号5 小时前
力扣热题100道之207课程表
算法·leetcode·职场和发展
这周也會开心5 小时前
Map的遍历方式
数据结构·算法