网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
我们来总结一下数组dp的定义:
dp[i][j]表示:对于前i个商品,当前背包容量为j的时候,能存放的最大价值为dp[i][j]
如果没有选择第i个商品,则最大价值就是dp[i - 1][j]
如果选择了第i个商品,则最大价值就为dp[i - 1][j - wt[i - 1]] + value[i - 1];
Notes:因为商品都是从1开始的,所以i-1表示第i个商品的索引
完整代码:
#include<vector>
#include <string>
#include <iostream>
#include <string>
using namespace std;
class Solution
{
public:
int knapsack(int W,int N,vector<int> &wt,vector<int> &value)
{
//初始化+base case
vector<vector<int>>dp(N + 1,vector<int>(W + 1,0));
for (int i = 1;i <= N;++i)
{
for (int j = 1;j <= W;++j)
{
if (j >= wt[i - 1])
dp[i][j] = max(dp[i - 1][j],dp[i - 1][j - wt[i - 1]] + value[i - 1]);
else
dp[i][j] = dp[i - 1][j];
}
}
return dp[N][W];
}
};
int main()
{
int W = 4,N = 3;
vector<int> wt = {4,3,1};
vector<int> value = {300,200,150};
Solution S;
int res = S.knapsack(W,N,wt,value);
cout<<res<<endl;
return 0;
}
我们发现的二维数组我们每次计算的时候都是只需要上一行的数字,其他的我们都用不到,所以我们可以用一维空间的数组来记录上一行的值即可,但要记住一维的时候一定要逆序,因为如果不逆顺序,数组前面的值更新以后就会覆盖后面的值,从而发生了重复计算:
#include<vector>
#include <string>
#include <iostream>
#include <string>
using namespace std;
class Solution
{
public:
int knapsack(int W,int N,vector<int> &wt,vector<int> &value)
{
vector<int>dp(W + 1,0);
for (int i = 1;i <= N;++i)
{
for (int j = W;j >= 1;--j)
{
if (j >= wt[i - 1])
dp[j] = max(dp[j],dp[j - wt[i - 1]] + value[i - 1]);
}
}
return dp[W];
}
};
int main()
{
int W = 4,N = 3;
vector<int> wt = {4,3,1};
vector<int> value = {300,200,150};
Solution S;
int res = S.knapsack(W,N,wt,value);
cout<<res<<endl;
return 0;
}
以上就是基本的0-1背包问题,在做题的过程中,基本明确了dp数组的定义,就可以顺理成章的推出递推公式,那么写代码就不在话下了。。。
子集背包问题
这道题眨眼一看和背包有个毛线关系啊,不过可以换一种思路:
可以先对nums数组求和,问题就转换成了,给你一个容量为sum/2的背包,有nums.size()个物品,每个物品的重量为nums[i],问你有没有一种装法,可以把背包装满,如果有返回true,否则返回false;
这样这个问题就转化为0-1背包问题了
接下来我们看dp数组的定义:
此题dp[i][j]表示前i个物品,当前背包重量为j,若dp[i][j] = true,则表示恰好可以装满,否则不能恰好装满
base case很好确定:
dp[N][0]:当背包容量为0的时候,什么都不用装,就相当于满了;dp[N][0] = true;
dp[0][sum / 2]:当背包没有物品,你咋装满?dp[0][sum / 2] = false;
状态转移方程就可以参照0-1背包,根据问题稍作修改即可:
1. 如果不把第i个物品装入背包,那么可不可以恰好装满,取决于上一个状态:dp[i - 1][j]
2. 如果把第i个物品装入背包,那么恰好装满取决于dp[i][j - nums[i - 1]];这里状态方程的意思就是:如果把第i个物品装进去,就看背包剩下的重量j - nums[i - 1]时,能否被刚好装满。
完整代码:
class Solution {
public:
bool canPartition(vector<int>& nums)
{
int sum = 0;
for (int c : nums)
sum += c;
if (sum & 1) //如果sum是奇数,就不用继续了,因为不能等分为2份呀。
return false;
//问题转化为背包问题
//有一个重量为sum/2的背包和nums.size()个物品,每个物品的重量的nums[i]
//判断有没有一种装法,能够恰好装满背包
vector<vector<bool >> dp(nums.size() + 1,vector<bool>(sum / 2 + 1,0));
//base case
for (int i = 0;i <= nums.size();++i)
dp[i][0] = true;
for (int i = 1;i <= nums.size();++i)
{
for (int j = 1;j <= sum / 2;++j)
{
if (j >= nums[i - 1])
dp[i][j] = dp[i - 1][j]|| dp[i - 1][j - nums[i - 1]];
else
dp[i][j] = dp[i - 1][j];
}
}
return dp[nums.size()][sum / 2];
}
};
状态压缩后:
class Solution {
public:
bool canPartition(vector<int> &nums)
{
int sum = 0;
for (int c : nums)
sum += c;
if (sum & 1)
return false;
vector<bool> dp(sum / 2 + 1,0);
//base case;
dp[0] = true;
for (int i = 0;i < nums.size();++i)
{
for (int j = sum / 2 ;j >= 0;--j)
{
if (j >= nums[i])
dp[j] = dp[j] || dp[j - nums[i]];
}
}
return dp[sum / 2];
}
};
完全背包问题
其实完全背包问题和上面两个背包问题的最大区别就是:每个物品的数量无限制。我们来看一道典型的完全背包问题
其实这道题还是类似于上面的解题步骤:
- 状态和选择
- dp数组的定义
- 推导状态转移方程
就直接说dp数组的定义吧:
dp[i][j]就表示了使用前i个硬币的价值,想凑到j的金额时,有dp[i][j]中凑法
base case就是dp[0][....] = 0,dp[....][0] = 1;
状态转移方程的思想还是类似于前面的背包问题
1. 如果不用coins[i]这个面值的硬币,dp[i][j]=dp[i-1][j];
2. 如果用conis[i]这个面值的硬币,dp[i][j]=dp[i-1][j-conis[i-1]];这个就是说如果你用conis[i]这个面值的硬币后,就只关心怎么凑出面额为j - coins[i - 1],就好比你已经用面值为2的硬币凑出7块钱,你如果知道了凑出5块钱的方法,再加上你那面值为2的硬币不就成了?
完整代码:
class Solution {
public:
int change(int amount, vector<int>& coins)


**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618658159)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
15665102906)]
[外链图片转存中...(img-pMohoRAV-1715665102907)]
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618658159)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**