二维费用的背包问题:大多以01背包为基础,存在两个限制条件!
一、一和零
class Solution {
public:
//需要满足两个条件的我们称之为二位费用的背包问题
int findMaxForm(vector<string>& strs, int m, int n) {
//dp[i][j][k]表示前i个字符串中选 0不超过j 1不超过k
//str[i-1]有a个0,b个1
//如果不选i dp[i][j][k]=dp[i-1][j][k]
//如果选i dp[i][j][k]=dp[i-1][j-a][k-b]+1
int len=strs.size();
vector<vector<vector<int>>> dp(len+1,vector<vector<int>>(m+1,vector<int>(n+1)));
for(int i=1;i<=len;++i)
{
//先统计一下其数量
int a=0,b=0;
for(auto&ch:strs[i-1])
if(ch=='0') ++a; else ++b;
for(int j=0;j<=m;++j) //只要保证i是从小到大的即可 j和k无所谓 因为会用到的是i-1那一面的值
for(int k=0;k<=n;++k)
{
dp[i][j][k]=dp[i-1][j][k];
if(j>=a&&k>=b) dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-a][k-b]+1);
}
}
return dp[len][m][n];
}
};
滚动数组优化一个维度
class Solution {
public:
int findMaxForm(vector<string>& strs, int m, int n) {
//dp[i][j][k]表示前i个字符串中选 0不超过j 1不超过k
//str[i-1]有a个0,b个1
//如果不选i dp[i][j][k]=dp[i-1][j][k]
//如果选i dp[i][j][k]=dp[i-1][j-a][k-b]+1
int len=strs.size();
vector<vector<int>> dp(m+1,vector<int>(n+1));
for(int i=1;i<=len;++i)
{
//先统计一下其数量
int a=0,b=0;
for(auto&ch:strs[i-1])
if(ch=='0') ++a; else ++b;
for(int j=m;j>=a;--j) //空间优化后要保证从大往小遍历
for(int k=n;k>=b;--k)
dp[j][k]=max(dp[j][k],dp[j-a][k-b]+1);
}
return dp[m][n];
}
};
二、盈利计划(非常经典)
class Solution {
public:
int profitableSchemes(int n, int m, vector<int>& g, vector<int>& p) {
//01背包问题 一个工作可以选择做或者不做profit 两个限制条件 一个是minProfit 一个是n
//dp[i][j][k] 从前i个工作中选 人数要不超过j 利润至少为k 的所有选择计划。
const int MOD=1e9+7;
int len=g.size();
vector<vector<vector<int>>> dp(len+1,vector<vector<int>>(n+1,vector<int>(m+1)));
//如果不做i工作 dp[i][j][k]=dp[i-1][j][k]
//如果做了i工作 dp[i][j][k]+=dp[i-1][j-g[i-1]][k-p[i-1]]
//分析初始化 如果i为0时 显然都为0 p为0时 有j=1
for(int j=0;j<=n;++j) dp[0][j][0]=1; //完成初始化 只需考虑i为0的情况即可 因为其他都会特判
//开始填表
for(int i=1;i<=len;++i) //只需考虑i即可
for(int j=0;j<=n;++j)
for(int k=0;k<=m;++k)
{
dp[i][j][k]=dp[i-1][j][k];
if(j>=g[i-1]) dp[i][j][k]+=dp[i-1][j-g[i-1]][max(k-p[i-1],0)];
dp[i][j][k]%=MOD;
}
return dp[len][n][m];
}
};
滚动数组优化维度:
class Solution {
public:
int profitableSchemes(int n, int m, vector<int>& g, vector<int>& p) {
//01背包问题 一个工作可以选择做或者不做profit 两个限制条件 一个是minProfit 一个是n
//dp[i][j][k] 从前i个工作中选 人数要不超过j 利润至少为k 的所有选择计划。
const int MOD=1e9+7;
int len=g.size();
vector<vector<int>> dp(n+1,vector<int>(m+1));
//如果不做i工作 dp[i][j][k]=dp[i-1][j][k]
//如果做了i工作 dp[i][j][k]+=dp[i-1][j-g[i-1]][k-p[i-1]]
//分析初始化 如果i为0时 显然都为0 p为0时 有j=1
for(int j=0;j<=n;++j) dp[j][0]=1; //完成初始化 只需考虑i为0的情况即可 因为其他都会特判
//开始填表
for(int i=1;i<=len;++i) //只需考虑i即可
for(int j=n;j>=g[i-1];--j)
for(int k=m;k>=0;--k)
{
dp[j][k]+=dp[j-g[i-1]][max(k-p[i-1],0)];
dp[j][k]%=MOD;
}
return dp[n][m];
}
};
三、组合总和IV(似包非包)
分析问题的过程中,发现重复子问题,然后抽象出一个状态表示
class Solution {
public:
//该题是排列总和
//背包问题本质上解决的是 有限制条件的组合问题
int combinationSum4(vector<int>& nums, int target) {
vector<double> dp(target + 1);
dp[0] = 1;
for (int i = 1; i <= target; i++)
for (int& num : nums)
if (num <= i) dp[i] += dp[i - num];
return dp[target];
}
};
四、不同的二叉搜索树(卡特兰数)
class Solution {
public:
int numTrees(int n) {
vector<int> dp(n+1);
dp[0]=1;//dp[i]表示i个节点时有多少种搜索树
for(int i=1;i<=n;++i)
for(int j=1;j<=i;++j)
dp[i]+=dp[j-1]*dp[i-j];
return dp[n];
//发现重复子问题,抽象出一种状态表示
}
};