例题1
小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的空间,并且具有不同的价值。
小明的行李空间为 N,问小明应该如何抉择,才能携带最大价值的研究材料,每种研究材料只能选择一次,并且只有选与不选两种选择,不能进行切割。
动规五部曲
1.确定dp数组以及下标的含义
dp[i][j] 的含义:在下标 [0~i] 的物品中任取,装进容量为 j 的背包,所获得的最大价值为 dp[i][j].
官方给的图:
2.确定dp数组的递推式
由1,当前dp[i][j]的状态是由上一个 dp[i-1][j] 推导来的,dp[i-1][j] 已经做好了决策,那么到dp[i][j]的时候,我们可以选择放当前下标为 i 的物品,也可以选择不放。
如放,dp[i][j]=dp[i][j-weight[j]]+value[i]
如不放,dp[i][j]=dp[i-1][j]
此时我们要求dp最大值,故 dp[i][j]=max(dp[i-1][j],dp[i][j-weight[j]]+value[i])
3.初始化dp数组
对于dp二维数组,我们通常将i=0,j=0的一行一列进行初始化
当i=0时,dp[0][j]表示从下标为0的物体中取物装进背包。这时若 j>= weight[0] ,则放入背包,反之dp [0][j]都为0
当j=0时,背包容量为0,此时dp[i][0]都为0,放不下任何物品。
4.确定遍历顺序
由递推式可知,有两个遍历的维度:物品与背包重量,先遍历哪个都行,因为dp[i][j]需要靠dp[i-1][j]推导,那么在这里先遍历物品i。
5.举例推导dp数组
做动态规划的题目,最好的过程就是自己在纸上举一个例子把对应的dp数组的数值推导一下,然后在动手写代码!
code
void test_2_wei_bag_problem1() {
vector<int> weight = {1, 3, 4};
vector<int> value = {15, 20, 30};
int bagweight = 4;
// 二维数组
vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));
// 初始化
for (int j = weight[0]; j <= bagweight; j++) {
dp[0][j] = value[0];
}
// weight数组的大小 就是物品个数
for(int i = 1; i < weight.size(); i++) { // 遍历物品
for(int j = 0; j <= bagweight; j++) { // 遍历背包容量
if (j < weight[i]) dp[i][j] = dp[i - 1][j];
else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
}
}
cout << dp[weight.size() - 1][bagweight] << endl;
}
int main() {
test_2_wei_bag_problem1();
}
例题2
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意:每个数组中的元素不会超过 100 数组的大小不会超过 200
动规五部曲
1.确定dp数组以及下标的含义
如题,若集合元素的和为sum,分割为两个子集,那么如果其中一个子集的集合元素和为sum/2,另一个子集元素的和自然而然也为sum/2,所以我们只需验证,集合中能否找出集合元素元素和为sum/2的序列,也就是一个容量为sum/2的背包是否能被填满 的0-1背包问题。
dp[j]的含义:容量为j的背包,所能装的物品的最大价值为dp[j]。这样,当dp[j] == sum/2时,就证明true。
2.确定dp数组的递推式
根据dp[j]的含义,dp[j]每次在 放当前物品 和 不放当前物品 中取最大值,而物品的重量和价值是同一个表达式,即nums[i]。故dp[j] = max(dp[j], dp[j - weight[i]] + values[i])
3.初始化dp数组
dp[0]=0,当j=0时,背包容量为0,放不下任何物品。
如果题目给的价值都是正整数那么非0下标都初始化为0就可以了,如果题目给的价值有负数,那么非0下标就要初始化为负无穷。
4.确定遍历顺序
二维dp遍历的时候,背包容量是从小到大,而一维dp遍历的时候,背包是从大到小。因为如果正序遍历背包的话,背包从小到大的过程中可能会放入多次的i相同的物品,导致一个背包放入了多次的相同物品。
那么可以先遍历背包再遍历物品吗?不可以。如果遍历背包容量放在上一层,那么每个dp[j]就只会放入一个物品,即:背包里只放入了一个物品。
倒序遍历的原因是,本质上还是一个对二维数组的遍历,并且右下角的值依赖上一层左上角的值,因此需要保证左边的值仍然是上一层的,从右向左覆盖
5.举例推导dp数组
code
class Solution {
public:
bool canPartition(vector<int>& nums) {
int n=nums.size();
int sum=0;
for(int i=0;i<n;i++)
sum += nums[i];
if(sum %2 != 0)return false;
//sort(nums.begin(),nums.end());
bool res=canfull(nums,nums,sum/2);
return res;
}
bool canfull(vector<int> weight,vector<int> value,int size)
{
bool res=false;
vector<int> dp(10001,0);
dp[0]=0;
for(int i=0;i<value.size();i++)
{
for(int j=size;j>= weight[i];j--)
dp[j] = max(dp[j],dp[j-weight[i]]+value[i]);
}
if(dp[size] == size)
res=true;
return res;
}
};