目录
1.1二维dp数组
1.2一维dp数组改进
1.3相关例题
1.3.1分割等和子集
1.3.2一和零
1.1二维dp数组
概述:背包的最大重量是固定的,物品的数量,重量也是固定的,并且物品只能放一次,可以选择放或者不放,最后要保证背包里面物品的最大价值。
例如:下面假设背包的最大重量是4.
解释:dp[i][j]的含义:从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
可以从两个方向去推导dp[i][j],放当前物品或者不放当前物品。
由dp[i - 1][j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是dp[i - 1][j]
由dp[i - 1][j - weight[i]]推出,dp[i - 1][j - weight[i]] 为背包容量为j - weight[i]且不放物品i的最大价值,那么dp[i - 1][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值,所以递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
开辟一个二维数组
当背包的容量为0和物品数量为0时,背包的价值都是0.
可以尝试先遍历物,1,可以认为当前只有一个物品,背包的容量为1-4,后面在去遍历物品2,背包的容量也是从1-4。例如:
完整代码:
vector<int> weight = { 1, 3, 4 };
vector<int> value = { 15, 20, 30 };
int bagWeight = 4;
// 二维数组,全初始化为0
vector<vector<int>> dp(weight.size() + 1, vector<int>(bagWeight + 1, 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]);
}
}
1.2一维dp数组改进
思路:
在使用二维数组的时候,递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
其实可以发现如果把dp[i - 1]那一层拷贝到dp[i]上,表达式完全可以是:dp[i][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i]);把dp[i - 1]这一层拷贝到dp[i]上,不如只用一个一维数组了,只用dp[j](一维数组,也可以理解是一个滚动数组)。
确定dp数组的定义:
在一维dp数组中,dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]。一维dp数组的递推公式
dp[j]可以通过dp[j - weight[j]]推导出来,dp[j - weight[i]]表示容量为j - weight[i]的背包所背的最大价值。dp[j - weight[i]] + value[i] 表示 容量为 j - 物品i重量 的背包 加上 物品i的价值。(也就是容量为j的背包,放入物品i了之后的价值)
dp[j]有两个选择,一个是取自己dp[j],一个是取dp[j - weight[i]] + value[i],取最大值,递归公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
假设也是先遍历物品,再去遍历背包。
例如:
在一维数组中,为了确保每个物品只能放入一次,遍历背包时,应该先去遍历背包的容量。例如:
代码:
for (int i = 0; i < weight.size(); i++) // 遍历物品
{
for (int j = bagWeight; j >= weight[i]; j--)
{
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); // 遍历背包容量
}
}
1.3相关例题
1.3.1分割等和子集
问题描述:给你一个 只包含正整数 的 非空 数组 nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
输入:nums = [1,5,11,5] 输出:true 解释:数组可以分割成 [1, 5, 5] 和 [11] 。
思路:使这两个子集的元素相等,也就是让子集的和为数组和的一半。其实可以认为子集和就是背包的最大容量,也是最大价值。问题等效于能否从数组中挑选若干个元素,使得元素总和等于所有元素总和的一半。也就是判断该背包装满后,最大容量装满后的价值是否恰好等于最大价值。
代码示例:
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum=0;
for(int i=0;i<nums.size();i++)
sum+=nums[i];
if(sum%2!=0)
return false;
sum/=2;
vector<vector<int>>dp(nums.size()+1,vector<int>(sum+1,0));
for(int i=1;i<=nums.size();i++)
{
for(int j=1;j<=sum;j++) //nums[i-1]为第i个物品
{
if(j<nums[i-1])
dp[i][j]=dp[i-1][j];
else
dp[i][j]=max(dp[i-1][j],dp[i-1][j-nums[i-1]]+nums[i-1]);
}
}
if(dp[nums.size()][sum]==sum)
return true;
return false;
}
};
一维dp数组改进:
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = 0;
for (int i = 0; i < nums.size(); i++)
{
sum += nums[i];
}
if (sum % 2 == 1) return false;
int target = sum / 2;
vector<int> dp(target + 1, 0);
// 开始 01背包
for (int i = 0; i < nums.size(); i++)
{
for (int j = target; j >= nums[i]; j--)
{
dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
}
}
if (dp[target] == target)
return true;
return false;
}
};
1.3.2一和零
问题描述:
给你一个二进制字符串数组 strs
和两个整数 m
和 n
。
请你找出并返回 strs
的最大子集的长度,该子集中 最多 有 m
个 0
和 n
个 1
。
如果 x
的所有元素也是 y
的元素,集合 x
是集合 y
的 子集 。
输入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3 输出:4 解释:最多有 5 个 0 和 3 个 1 的最大子集是 {"10","0001","1","0"} ,因此答案是 4 。 其他满足题意但较小的子集包括 {"0001","1"} 和 {"10","1","0"} 。{"111001"} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。
这也是一道01背包问题,元素的个数是确定的,背包的容量也是确定的,只不过该背包的容量有两个维度罢了。本质就是求背包放满后,可以放入的元素个数最多是多少。
class Solution {
public:
int findMaxForm(vector<string>& strs, int m, int n) {//m个0,n个1
vector<vector<int>> v(m+1,vector<int>(n+1,0));
for(auto s:strs)
{ int p1=0;
int p0=0;
for(auto ch: s) //先遍历物品
{
if(ch=='0')
p0++;
else
p1++;
}
for(int i=1;i<=m;i++) /后遍历背包容量,背包容量,用一个二维数组表示
{
for(int j=1;j<=n;j++)
{
if(i>p0&&j>p1) //说明能放下该物品
{
v[i][j]=max(v[i][j],v[i-p0][j-p1]+1);
}
}
}
}
return v[m][n];
}
};