完全背包理论基础
文章链接
理论基础
由于之前说过0-1背包的理论基础(滚动数组解法),所以很容易就知道完全背包的写法,完全背包的含义是每个物品都可以用多次,代码如下:
function foo(weight: number[], value: number[], size: number) {
let dp: number[] = new Array(size + 1).fill(0)
for (let i = 0; i < weight.length; i++) {
for (let j = weight[i]; j <=size; j++) {//这里为正序 就可以一个物品使用多次
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i])
}
}
console.log(dp)
console.log(dp[size])
}
foo([1, 3, 4], [15, 20, 30], 4)
同时,文章也讲解了遍历顺序的问题,如果是纯完全背包问题,则两层的遍历顺序是可以转变的,可以先遍历背包,也可以先遍历物品,下面是先遍历物品的图(行遍历):
先遍历背包的图(列)
518. 零钱兑换 II
链接
文章链接
题目链接
第一想法
这道题看完题后,发现和目标和这道题是挺类似的,不一样的是这道题每个元素可以无穷使用,那么接下来递归五部曲
- dp数组的含义,dp[i]为和为i的组合数
- 初始化,其他的都为0,只有dp[0]=1,原因是让递推公式可以顺利递推,防止递推公式的结果都是0
- 递推公式,和目标和一样dp[j] += dp[j - coins[i]] 原因在目标和中写了 例如和为5的组合数等于元素为1时(和为4的组合数)+元素为2时(和为3的组合数)+....元素为4时(和为1的组合数)之和
- 遍历顺序 还是使用的是先物品再背包
function change(amount: number, coins: number[]): number {
let dp: number[] = new Array(amount + 1).fill(0)
dp[0] = 1
for (let i = 0; i < coins.length; i++) {
for (let j = coins[i]; j <= amount; j++) {
dp[j] += dp[j - coins[i]]//组合数相加求解
}
}
return dp[amount]
}
看完文章后的想法
文章的想法和我的想法是一致的,但是! 文章的讲解中使我发现我没有考虑到的,我这里是先物品再背包,所以能出现正确答案,但是先背包再物品的话是不可以的!!
例如:coins[1,3],amount=4,先物品再背包得到结果为
dp | 容量0 | 容量1 | 容量2 | 容量3 | 容量4 |
---|---|---|---|---|---|
coins[0] | 1 | 1 | 1 | 1 | 1 |
coins[1] | 1 | 1 | 1 | 2 | 2 |
这样结果组合为4只包含{1,1,1,1}和{1,3}两种情况
如果先遍历背包再物品的话,代码为:
for (let j = 0; j <= amount; j++) { // 遍历背包容量
for (let i = 0; i < coins.length; i++) { // 遍历物品
if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]];
}
}
结果为(列为背包遍历,行为dp数组的值)
dp | 容量0 | 容量1 | 容量2 | 容量3 | 容量4 |
---|---|---|---|---|---|
容量0 | 1 | 0 | 0 | 0 | 0 |
容量1 | 1 | 1 | 0 | 0 | 0 |
容量2 | 1 | 1 | 1 | 0 | 0 |
容量3 | 1 | 1 | 1 | 2 | 0 |
容量4 | 1 | 1 | 1 | 2 | 3 |
这样结果组合为4只包含{1,1,1,1}和{1,3}和{3,1}三种情况,所以先物品再背包像是组合,先背包再物品像是排列
思考
这道题能写出来,但是,对于先背包还是先物品会导致结果的不同我是没想到的,通过文章的讲解我发现了先物品就是想组合,而先背包就像排列
377. 组合总和 Ⅳ
链接
文章链接
题目链接
第一想法
刚刚在零钱兑换II这道题解释了先遍历背包和先遍历物品的不同,所以这道题很轻松的就写出来了,递归五部曲为:
- dp数组的含义,dp[j]表示和为j的组合数
- 初始化,还是dp[0]=1,其他为0
- 递推公式:
dp[j] += dp[j - nums[i]]
- 遍历顺序,这道题由题可知,不同的排列算不同的组合,所以先遍历背包
function combinationSum4(nums: number[], target: number): number {
let dp: number[] = new Array(target + 1).fill(0)
dp[0] = 1
for (let j = 0; j <= target; j++) {
for (let i = 0; i < nums.length; i++) {
if (j >= nums[i]) dp[j] += dp[j - nums[i]]
}
}
return dp[target]
}
看完文字后的想法
文章的想法我上面已经说了,所以这里就不加赘述了,下面附一张代码随想录中的图来解释一下先遍历背包的情况(为什么是排列)
思考
这道题之前在零钱兑换 II这道题中已经想清楚了,所以这道题很快就想到用排列而不是组合,所以困难。
今日总结
今日学习了完全背包理论,因为再0-1背包滚动数组的解法中就了解过,所以就比较好理解今天的内容。同时了解了先遍历背包和先遍历物品和是组合还是排列问题有关,今日耗时2.5小时