0/1背包
有n件物品和一个最大负重为w的背包。其中第i件物品的重量是weight[i],价值是value[i]。每件物品只能用一次,求使包内物品价值最大的背包方案。
二维dp数组:
solution
int GetMaxValue2D(int[] weight, int[] value, int w, int n)
{
// 创建一个二维数组 dp,大小为 (n + 1) x (w + 1)
int[,] dp = new int[n + 1, w + 1];
// 显式初始化 dp 数组(可选,C# 中会自动初始化为 0)
// for (int i = 0; i <= n; i++)
// {
// for (int j = 0; j <= w; j++)
// {
// dp[i, j] = 0;
// }
// 遍历所有物品
for (int i = 1; i <= n; i++)
{
// 遍历所有背包容量
for (int j = 0; j <= w; j++)
{
if (j >= weight[i - 1])
{
// 当前背包容量 j 足够容纳第 i 件物品
dp[i, j] = Math.Max(dp[i - 1, j], dp[i - 1, j - weight[i - 1]] + value[i - 1]);
}
else
{
// 当前背包容量 j 不足以容纳第 i 件物品
dp[i, j] = dp[i - 1, j];
}
}
}
// 返回背包最大容量 w 下的最大价值
return dp[n, w];
}
dp推导:
1、dp[i][j]:背包负重为 j 时,从下标为 0-i 的物品中取用的最大价值
2、递推公式:
物品 i 不取用:背包负重仍为 j,在物品 0 - i-1 中取用的最大价值
物品 i 取用:物品 i 占用 weight[i] 负重, 剩余负重 j - weight[i],在物品 0 - i-1 中取用的最大价值
在以上两种方案中取较大值
dp[i][j] = max(dp[i - 1][j] + value[i] + dp[i - 1][j - weight[i]])
3、dp数组初始化:第一行和第一列
dp[i][0] = 0;
dp[0][j] = j >= weight[0]? value[0] : 0
一维dp数组(滚动数组)
solution
int GetMaxValue1D(int[] weight, int[] value, int w, int n)
{
int[] dp = new int[w + 1];
for (int i = 0; i < n; i++)
{
for (int j = w; j >= weight[i]; j--)
{
dp[j] = Math.Max(dp[j], dp[j - weight[i]] + value[i]);
}
}
return dp[w];
}
dp推导:
1、dp[i]:背包负重为 i 时,可以实现的最大价值为dp[i]
2、递推公式:
dp[i]:不放物品j,遍历到j时,dp数组是取用到物品 j-1 时的最大价值
dp[i] = max( dp[i], value[ j ] + dp[ i - weight[ j ] ] )
3、dp数组初始化:初始化为0
4、遍历方式:
先遍历物品再遍历背包
从上往下滚动(正序)(遍历物品)
从大往小遍历(倒序)(遍历背包)
416. 分割等和子集
题目:416. 分割等和子集 - 力扣(LeetCode)
题解:代码随想录 (programmercarl.com)
0/1背包的应用,递推公式的原理比较好像,遍历方式不好想
solution
public class Solution {
public bool CanPartition(int[] nums) {
int totalSum = nums.Sum();
// 如果总和是奇数,不能分割成两个相等的子集
if (totalSum % 2 != 0) {
return false;
}
int target = totalSum / 2;
bool[] dp = new bool[target + 1];
dp[0] = true;
foreach (int num in nums) {
for (int i = target; i >= num; i--) {
dp[i] = dp[i] || dp[i - num];
}
}
return dp[target];
}
}
summary
dp:
1、dp[i]: 从nums数组里不重复的取数能否使和等于i
2、递推公式:
dp[i] = dp[i] || dp[i - num]
3、初始化:
dp[0] = true
4、遍历方式: