diff --git a/problems/背包问题完全背包一维.md b/problems/背包问题完全背包一维.md new file mode 100644 index 00000000..a8e241c3 --- /dev/null +++ b/problems/背包问题完全背包一维.md @@ -0,0 +1,211 @@ + +# 完全背包-一维数组 + +本题力扣上没有原题,大家可以去[卡码网第52题](https://kamacoder.com/problempage.php?pid=1052)去练习。 + +## 算法公开课 + +**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[带你学透完全背包问题! ](https://www.bilibili.com/video/BV1uK411o7c9/),相信结合视频再看本篇题解,更有助于大家对本题的理解**。 + + +## 思路 + +本篇我们不再做五部曲分析,核心内容 在 01背包二维 、01背包一维 和 完全背包二维 的讲解中都讲过了。 + +上一篇我们刚刚讲了完全背包二维DP数组的写法: + +```CPP +for (int i = 1; i < n; 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][j - weight[i]] + value[i]); + } +} +``` + +压缩成一维DP数组,也就是将上一层拷贝到当前层。 + +将上一层dp[i-1] 的那一层拷贝到 当前层 dp[i] ,那么 递推公式由:`dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i])` 变成: `dp[i][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i])` + +这里有录友想,这样拷贝的话, dp[i - 1][j] 的数值会不会 覆盖了 dp[i][j] 的数值呢? + +并不会,因为 当前层 dp[i][j] 是空的,是没有计算过的。 + +变成 `dp[i][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i])` 我们压缩成一维dp数组,去掉 i 层数维度。 + +即:`dp[j] = max(dp[j], dp[j - weight[i]] + value[i])` + + +接下来我们重点讲一下遍历顺序。 + +看过这两篇的话: + +* [01背包理论基础(二维数组)](https://programmercarl.com/背包理论基础01背包-1.html) +* [01背包理论基础(一维数组)](https://programmercarl.com/背包理论基础01背包-2.html) + +就知道了,01背包中二维dp数组的两个for遍历的先后循序是可以颠倒了,一维dp数组的两个for循环先后循序一定是先遍历物品,再遍历背包容量。 + +**在完全背包中,对于一维dp数组来说,其实两个for循环嵌套顺序是无所谓的**! + +因为dp[j] 是根据 下标j之前所对应的dp[j]计算出来的。 只要保证下标j之前的dp[j]都是经过计算的就可以了。 + +遍历物品在外层循环,遍历背包容量在内层循环,状态如图: + +![动态规划-完全背包1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210126104529605.jpg) + +遍历背包容量在外层循环,遍历物品在内层循环,状态如图: + +![动态规划-完全背包2](https://code-thinking-1253855093.file.myqcloud.com/pics/20210729234011.png) + +看了这两个图,大家就会理解,完全背包中,两个for循环的先后循序,都不影响计算dp[j]所需要的值(这个值就是下标j之前所对应的dp[j])。 + +先遍历背包再遍历物品,代码如下: + +```CPP +for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量 + for(int i = 0; i < weight.size(); i++) { // 遍历物品 + if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); + } + cout << endl; +} +``` + +先遍历物品再遍历背包: + +```CPP +for(int i = 0; i < weight.size(); i++) { // 遍历物品 + for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量 + if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); + } +} +``` + +整体代码如下: + +```cpp +#include +#include +using namespace std; + +int main() { + int N, bagWeight; + cin >> N >> bagWeight; + vector weight(N, 0); + vector value(N, 0); + for (int i = 0; i < N; i++) { + int w; + int v; + cin >> w >> v; + weight[i] = w; + value[i] = v; + } + + vector dp(bagWeight + 1, 0); + for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量 + for(int i = 0; i < weight.size(); i++) { // 遍历物品 + if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); + } + } + cout << dp[bagWeight] << endl; + + return 0; +} +``` + + + +## 总结 + +细心的同学可能发现,**全文我说的都是对于纯完全背包问题,其for循环的先后循环是可以颠倒的!** + +但如果题目稍稍有点变化,就会体现在遍历顺序上。 + +如果问装满背包有几种方式的话? 那么两个for循环的先后顺序就有很大区别了,而leetcode上的题目都是这种稍有变化的类型。 + +这个区别,我将在后面讲解具体leetcode题目中给大家介绍,因为这块如果不结合具题目,单纯的介绍原理估计很多同学会越看越懵! + +别急,下一篇就是了! + +最后,**又可以出一道面试题了,就是纯完全背包,要求先用二维dp数组实现,然后再用一维dp数组实现,最后再问,两个for循环的先后是否可以颠倒?为什么?** + +这个简单的完全背包问题,估计就可以难住不少候选人了。 + + +## 其他语言版本 + +### Java: + +```java +import java.util.Scanner; + +public class Main { + public static void main(String[] args) { + Scanner scanner = new Scanner(System.in); + int N = scanner.nextInt(); + int bagWeight = scanner.nextInt(); + + int[] weight = new int[N]; + int[] value = new int[N]; + for (int i = 0; i < N; i++) { + weight[i] = scanner.nextInt(); + value[i] = scanner.nextInt(); + } + + int[] dp = new int[bagWeight + 1]; + + for (int j = 0; j <= bagWeight; j++) { // 遍历背包容量 + for (int i = 0; i < weight.length; i++) { // 遍历物品 + if (j >= weight[i]) { + dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]); + } + } + } + + System.out.println(dp[bagWeight]); + scanner.close(); + } +} + +``` + + + +### Python: + +```python +def complete_knapsack(N, bag_weight, weight, value): + dp = [0] * (bag_weight + 1) + + for j in range(bag_weight + 1): # 遍历背包容量 + for i in range(len(weight)): # 遍历物品 + if j >= weight[i]: + dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) + + return dp[bag_weight] + +# 输入 +N, bag_weight = map(int, input().split()) +weight = [] +value = [] +for _ in range(N): + w, v = map(int, input().split()) + weight.append(w) + value.append(v) + +# 输出结果 +print(complete_knapsack(N, bag_weight, weight, value)) + + +``` + + +### Go: + +```go + +``` +### Javascript: + +```Javascript +``` + diff --git a/problems/背包问题理论基础完全背包.md b/problems/背包问题理论基础完全背包.md index 3a50ee7b..1e270555 100644 --- a/problems/背包问题理论基础完全背包.md +++ b/problems/背包问题理论基础完全背包.md @@ -5,18 +5,11 @@

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们受益!

-# 动态规划:完全背包理论基础 +# 完全背包理论基础-二维DP数组 本题力扣上没有原题,大家可以去[卡码网第52题](https://kamacoder.com/problempage.php?pid=1052)去练习,题意是一样的。 -## 算法公开课 - -**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[带你学透完全背包问题! ](https://www.bilibili.com/video/BV1uK411o7c9/),相信结合视频再看本篇题解,更有助于大家对本题的理解**。 - -## 思路 - -### 完全背包 - +## 完全背包 有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。**每件物品都有无限个(也就是可以放入背包多次)**,求解将哪些物品装入背包里物品价值总和最大。 @@ -24,14 +17,12 @@ 同样leetcode上没有纯完全背包问题,都是需要完全背包的各种应用,需要转化成完全背包问题,所以我这里还是以纯完全背包问题进行讲解理论和原理。 -在下面的讲解中,我依然举这个例子: +在下面的讲解中,我拿下面数据举例子: -背包最大重量为4。 - -物品为: +背包最大重量为4,物品为: | | 重量 | 价值 | -| --- | --- | --- | +| ----- | ---- | ---- | | 物品0 | 1 | 15 | | 物品1 | 3 | 20 | | 物品2 | 4 | 30 | @@ -40,471 +31,292 @@ 问背包能背的物品最大价值是多少? -01背包和完全背包唯一不同就是体现在遍历顺序上,所以本文就不去做动规五部曲了,我们直接针对遍历顺序经行分析! +**如果没看到之前的01背包讲解,已经要先仔细看如下两篇,01背包是基础,本篇在讲解完全背包,之前的背包基础我将不会重复讲解**。 -关于01背包我如下两篇已经进行深入分析了: +* [01背包理论基础(二维数组)](https://programmercarl.com/背包理论基础01背包-1.html) +* [01背包理论基础(一维数组)](https://programmercarl.com/背包理论基础01背包-2.html) -* [动态规划:关于01背包问题,你该了解这些!](https://programmercarl.com/背包理论基础01背包-1.html) -* [动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://programmercarl.com/背包理论基础01背包-2.html) +动规五部曲分析完全背包,为了从原理上讲清楚,我们先从二维dp数组分析: -首先再回顾一下01背包的核心代码 -```cpp -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. 确定dp数组以及下标的含义 + +**dp[i][j] 表示从下标为[0-i]的物品,每个物品可以取无限次,放进容量为j的背包,价值总和最大是多少**。 + +很多录友也会疑惑,凭什么上来就定义 dp数组,思考过程是什么样的, 这个思考过程我在 [01背包理论基础(二维数组)](https://programmercarl.com/背包理论基础01背包-1.html) 中的 “确定dp数组以及下标的含义” 有详细讲解。 + + +### 2. 确定递推公式 + +这里在把基本信息给出来: + +| | 重量 | 价值 | +| ----- | ---- | ---- | +| 物品0 | 1 | 15 | +| 物品1 | 3 | 20 | +| 物品2 | 4 | 30 | + +对于递推公式,首先我们要明确有哪些方向可以推导出 dp[i][j]。 + +这里依然拿dp[1][4]的状态来举例: ([01背包理论基础(二维数组)](https://programmercarl.com/背包理论基础01背包-1.html) 中也是这个例子,要注意下面的不同之处) + +求取 dp[1][4] 有两种情况: + +1. 放物品1 +2. 还是不放物品1 + +如果不放物品1, 那么背包的价值应该是 dp[0][4] 即 容量为4的背包,只放物品0的情况。 + +推导方向如图: + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20241126112952.png) + +如果放物品1, **那么背包要先留出物品1的容量**,目前容量是4,物品1 的容量(就是物品1的重量)为3,此时背包剩下容量为1。 + +容量为1,只考虑放物品0 和物品1 的最大价值是 dp[1][1], **注意 这里和 [01背包理论基础(二维数组)](https://programmercarl.com/背包理论基础01背包-1.html) 有所不同了**! + +在 [01背包理论基础(二维数组)](https://programmercarl.com/背包理论基础01背包-1.html) 中,背包先空留出物品1的容量,此时容量为1,只考虑放物品0的最大价值是 dp[0][1],**因为01背包每个物品只有一个,既然空出物品1,那背包中也不会再有物品1**! + +而在完全背包中,物品是可以放无限个,所以 即使空出物品1空间重量,那背包中也可能还有物品1,所以此时我们依然考虑放 物品0 和 物品1 的最大价值即: **dp[1][1], 而不是 dp[0][1]** + +所以 放物品1 的情况 = dp[1][1] + 物品1 的价值,推导方向如图: + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20241126113104.png) + + +(**注意上图和 [01背包理论基础(二维数组)](https://programmercarl.com/背包理论基础01背包-1.html) 中的区别**,对于理解完全背包很重要) + +两种情况,分别是放物品1 和 不放物品1,我们要取最大值(毕竟求的是最大价值) + +`dp[1][4] = max(dp[0][4], dp[1][1] + 物品1 的价值) ` + +以上过程,抽象化如下: + +* **不放物品i**:背包容量为j,里面不放物品i的最大价值是dp[i - 1][j]。 + +* **放物品i**:背包空出物品i的容量后,背包容量为j - weight[i],dp[i][j - weight[i]] 为背包容量为j - weight[i]且不放物品i的最大价值,那么dp[i][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值 + +递归公式: `dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]);` + +(注意,完全背包二维dp数组 和 01背包二维dp数组 递推公式的区别,01背包中是 `dp[i - 1][j - weight[i]] + value[i])`) + +### 3. dp数组如何初始化 + +**关于初始化,一定要和dp数组的定义吻合,否则到递推公式的时候就会越来越乱**。 + +首先从dp[i][j]的定义出发,如果背包容量j为0的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为0。如图: + +![动态规划-背包问题2](https://code-thinking-1253855093.file.myqcloud.com/pics/2021011010304192.png) + +在看其他情况。 + +状态转移方程 `dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]);` 可以看出有一个方向 i 是由 i-1 推导出来,那么i为0的时候就一定要初始化。 + +dp[0][j],即:存放编号0的物品的时候,各个容量的背包所能存放的最大价值。 + +那么很明显当 `j < weight[0]`的时候,dp[0][j] 应该是 0,因为背包容量比编号0的物品重量还小。 + +当`j >= weight[0]`时,**dp[0][j] 如果能放下weight[0]的话,就一直装,每一种物品有无限个**。 + +代码初始化如下: + +```CPP +for (int i = 1; i < weight.size(); i++) { // 当然这一步,如果把dp数组预先初始化为0了,这一步就可以省略,但很多同学应该没有想清楚这一点。 + dp[i][0] = 0; +} + +// 正序遍历,如果能放下就一直装物品0 +for (int j = weight[0]; j <= bagWeight; j++) + dp[0][j] = dp[0][j - weight[0]] + value[0]; +``` + +(注意上面初始化和 [01背包理论基础(二维数组)](https://programmercarl.com/背包理论基础01背包-1.html)的区别在于物品有无限个) + + +此时dp数组初始化情况如图所示: + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20241114161608.png) + +dp[0][j] 和 dp[i][0] 都已经初始化了,那么其他下标应该初始化多少呢? + +其实从递归公式: dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]); 可以看出dp[i][j] 是由上方和左方数值推导出来了,那么 其他下标初始为什么数值都可以,因为都会被覆盖。 + +但只不过一开始就统一把dp数组统一初始为0,更方便一些。 + +最后初始化代码如下: + +```CPP +// 初始化 dp +vector> dp(weight.size(), vector(bagweight + 1, 0)); +for (int j = weight[0]; j <= bagWeight; j++) { + dp[0][j] = dp[0][j - weight[0]] + value[0]; } ``` -我们知道01背包内嵌的循环是从大到小遍历,为了保证每个物品仅被添加一次。 -而完全背包的物品是可以添加多次的,所以要从小到大去遍历,即: +### 4. 确定遍历顺序 -```CPP -// 先遍历物品,再遍历背包 -for(int i = 0; i < weight.size(); i++) { // 遍历物品 - for(int j = weight[i]; j <= bagWeight ; j++) { // 遍历背包容量 - dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); +[01背包理论基础(二维数组)](https://programmercarl.com/背包理论基础01背包-1.html) 中我们讲过,01背包二维DP数组,先遍历物品还是先遍历背包都是可以的。 - } -} -``` +因为两种遍历顺序,对于二维dp数组来说,递推公式所需要的值,二维dp数组里对应的位置都有。 -至于为什么,我在[动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://programmercarl.com/背包理论基础01背包-2.html)中也做了讲解。 +详细可以看 [01背包理论基础(二维数组)](https://programmercarl.com/背包理论基础01背包-1.html) 中的 【遍历顺序】的讲解 -dp状态图如下: - - -![动态规划-完全背包](https://code-thinking-1253855093.file.myqcloud.com/pics/20210126104510106.jpg) - -相信很多同学看网上的文章,关于完全背包介绍基本就到为止了。 - -**其实还有一个很重要的问题,为什么遍历物品在外层循环,遍历背包容量在内层循环?** - -这个问题很多题解关于这里都是轻描淡写就略过了,大家都默认 遍历物品在外层,遍历背包容量在内层,好像本应该如此一样,那么为什么呢? - -难道就不能遍历背包容量在外层,遍历物品在内层? - - -看过这两篇的话: -* [动态规划:关于01背包问题,你该了解这些!](https://programmercarl.com/背包理论基础01背包-1.html) -* [动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://programmercarl.com/背包理论基础01背包-2.html) - -就知道了,01背包中二维dp数组的两个for遍历的先后循序是可以颠倒了,一维dp数组的两个for循环先后循序一定是先遍历物品,再遍历背包容量。 - -**在完全背包中,对于一维dp数组来说,其实两个for循环嵌套顺序是无所谓的!** - -因为dp[j] 是根据 下标j之前所对应的dp[j]计算出来的。 只要保证下标j之前的dp[j]都是经过计算的就可以了。 - -遍历物品在外层循环,遍历背包容量在内层循环,状态如图: - - -![动态规划-完全背包1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210126104529605.jpg) - -遍历背包容量在外层循环,遍历物品在内层循环,状态如图: - -![动态规划-完全背包2](https://code-thinking-1253855093.file.myqcloud.com/pics/20210729234011.png) - -看了这两个图,大家就会理解,完全背包中,两个for循环的先后循序,都不影响计算dp[j]所需要的值(这个值就是下标j之前所对应的dp[j])。 - -先遍历背包在遍历物品,代码如下: - -```CPP -// 先遍历背包,再遍历物品 -for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量 - for(int i = 0; i < weight.size(); i++) { // 遍历物品 - if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); - } - cout << endl; -} -``` - -完整的C++测试代码如下: - -```CPP -// 先遍历物品,在遍历背包 -void test_CompletePack() { - vector weight = {1, 3, 4}; - vector value = {15, 20, 30}; - int bagWeight = 4; - vector dp(bagWeight + 1, 0); - for(int i = 0; i < weight.size(); i++) { // 遍历物品 - for(int j = weight[i]; j <= bagWeight; j++) { // 遍历背包容量 - dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); - } - } - cout << dp[bagWeight] << endl; -} -int main() { - test_CompletePack(); -} - -``` - -```CPP - -// 先遍历背包,再遍历物品 -void test_CompletePack() { - vector weight = {1, 3, 4}; - vector value = {15, 20, 30}; - int bagWeight = 4; - - vector dp(bagWeight + 1, 0); +所以既可以 先遍历物品再遍历背包: +```CPP +for (int i = 1; i < n; i++) { // 遍历物品 for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量 - for(int i = 0; i < weight.size(); i++) { // 遍历物品 - if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); - } + if (j < weight[i]) dp[i][j] = dp[i - 1][j]; + else dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]); } - cout << dp[bagWeight] << endl; } -int main() { - test_CompletePack(); -} - ``` -本题力扣上没有原题,大家可以去[卡码网第52题](https://kamacoder.com/problempage.php?pid=1052)去练习,题意是一样的,C++代码如下: +也可以 先遍历背包再遍历物品: -```cpp +```CPP +for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量 + for (int i = 1; i < n; i++) { // 遍历物品 + if (j < weight[i]) dp[i][j] = dp[i - 1][j]; + else dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]); + } +} +``` + +### 5. 举例推导dp数组 + +以本篇举例数据为例,填满了dp二维数组如图: + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20241126113752.png) + +因为 物品0 的性价比是最高的,而且 在完全背包中,每一类物品都有无限个,所以有无限个物品0,既然物品0 性价比最高,当然是优先放物品0。 + + +### 本题代码: + + +```CPP #include #include using namespace std; -// 先遍历背包,再遍历物品 -void test_CompletePack(vector weight, vector value, int bagWeight) { +int main() { + int n, bagWeight; + int w, v; + cin >> n >> bagWeight; + vector weight(n); + vector value(n); + for (int i = 0; i < n; i++) { + cin >> weight[i] >> value[i]; + } - vector dp(bagWeight + 1, 0); + vector> dp(n, vector(bagWeight + 1, 0)); - for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量 - for(int i = 0; i < weight.size(); i++) { // 遍历物品 - if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); + // 初始化 + for (int j = weight[0]; j <= bagWeight; j++) + dp[0][j] = dp[0][j - weight[0]] + value[0]; + + for (int i = 1; i < n; 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][j - weight[i]] + value[i]); } } - cout << dp[bagWeight] << endl; -} -int main() { - int N, V; - cin >> N >> V; - vector weight; - vector value; - for (int i = 0; i < N; i++) { - int w; - int v; - cin >> w >> v; - weight.push_back(w); - value.push_back(v); - } - test_CompletePack(weight, value, V); + + cout << dp[n - 1][bagWeight] << endl; + return 0; } + ``` - - -## 总结 - -细心的同学可能发现,**全文我说的都是对于纯完全背包问题,其for循环的先后循环是可以颠倒的!** - -但如果题目稍稍有点变化,就会体现在遍历顺序上。 - -如果问装满背包有几种方式的话? 那么两个for循环的先后顺序就有很大区别了,而leetcode上的题目都是这种稍有变化的类型。 - -这个区别,我将在后面讲解具体leetcode题目中给大家介绍,因为这块如果不结合具题目,单纯的介绍原理估计很多同学会越看越懵! - -别急,下一篇就是了! - -最后,**又可以出一道面试题了,就是纯完全背包,要求先用二维dp数组实现,然后再用一维dp数组实现,最后再问,两个for循环的先后是否可以颠倒?为什么?** -这个简单的完全背包问题,估计就可以难住不少候选人了。 - - - +关于一维dp数组,大家看这里:[完全背包一维dp数组讲解](./背包问题完全背包一维.md) ## 其他语言版本 -### Java: +### Java -```java -//先遍历物品,再遍历背包 -private static void testCompletePack(){ - int[] weight = {1, 3, 4}; - int[] value = {15, 20, 30}; - int bagWeight = 4; - int[] dp = new int[bagWeight + 1]; - for (int i = 0; i < weight.length; i++){ // 遍历物品 - for (int j = weight[i]; j <= bagWeight; j++){ // 遍历背包容量 - dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]); +```Java +import java.util.Scanner; + +public class Main { + public static void main(String[] args) { + Scanner scanner = new Scanner(System.in); + int n = scanner.nextInt(); + int bagWeight = scanner.nextInt(); + + int[] weight = new int[n]; + int[] value = new int[n]; + + for (int i = 0; i < n; i++) { + weight[i] = scanner.nextInt(); + value[i] = scanner.nextInt(); } - } - for (int maxValue : dp){ - System.out.println(maxValue + " "); - } -} -//先遍历背包,再遍历物品 -private static void testCompletePackAnotherWay(){ - int[] weight = {1, 3, 4}; - int[] value = {15, 20, 30}; - int bagWeight = 4; - int[] dp = new int[bagWeight + 1]; - for (int i = 1; i <= bagWeight; i++){ // 遍历背包容量 - for (int j = 0; j < weight.length; j++){ // 遍历物品 - if (i - weight[j] >= 0){ - dp[i] = Math.max(dp[i], dp[i - weight[j]] + value[j]); - } + int[][] dp = new int[n][bagWeight + 1]; + + // 初始化 + for (int j = weight[0]; j <= bagWeight; j++) { + dp[0][j] = dp[0][j - weight[0]] + value[0]; } - } - for (int maxValue : dp){ - System.out.println(maxValue + " "); - } -} -``` - - -### Python: - -先遍历物品,再遍历背包(无参版) -```python -def test_CompletePack(): - weight = [1, 3, 4] - value = [15, 20, 30] - bagWeight = 4 - dp = [0] * (bagWeight + 1) - for i in range(len(weight)): # 遍历物品 - for j in range(weight[i], bagWeight + 1): # 遍历背包容量 - dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) - print(dp[bagWeight]) - -test_CompletePack() - -``` - -先遍历物品,再遍历背包(有参版) -```python -def test_CompletePack(weight, value, bagWeight): - dp = [0] * (bagWeight + 1) - for i in range(len(weight)): # 遍历物品 - for j in range(weight[i], bagWeight + 1): # 遍历背包容量 - dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) - return dp[bagWeight] - -if __name__ == "__main__": - weight = [1, 3, 4] - value = [15, 20, 30] - bagWeight = 4 - result = test_CompletePack(weight, value, bagWeight) - print(result) - -``` -先遍历背包,再遍历物品(无参版) -```python -def test_CompletePack(): - weight = [1, 3, 4] - value = [15, 20, 30] - bagWeight = 4 - - dp = [0] * (bagWeight + 1) - - for j in range(bagWeight + 1): # 遍历背包容量 - for i in range(len(weight)): # 遍历物品 - if j - weight[i] >= 0: - dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) - - print(dp[bagWeight]) - -test_CompletePack() - - -``` - -先遍历背包,再遍历物品(有参版) -```python -def test_CompletePack(weight, value, bagWeight): - dp = [0] * (bagWeight + 1) - for j in range(bagWeight + 1): # 遍历背包容量 - for i in range(len(weight)): # 遍历物品 - if j - weight[i] >= 0: - dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) - return dp[bagWeight] - - -if __name__ == "__main__": - weight = [1, 3, 4] - value = [15, 20, 30] - bagWeight = 4 - result = test_CompletePack(weight, value, bagWeight) - print(result) - -``` - -### Go: - -```go - -// test_CompletePack1 先遍历物品, 在遍历背包 -func test_CompletePack1(weight, value []int, bagWeight int) int { - // 定义dp数组 和初始化 - dp := make([]int, bagWeight+1) - // 遍历顺序 - for i := 0; i < len(weight); i++ { - // 正序会多次添加 value[i] - for j := weight[i]; j <= bagWeight; j++ { - // 推导公式 - dp[j] = max(dp[j], dp[j-weight[i]]+value[i]) - // debug - //fmt.Println(dp) - } - } - return dp[bagWeight] -} - -// test_CompletePack2 先遍历背包, 在遍历物品 -func test_CompletePack2(weight, value []int, bagWeight int) int { - // 定义dp数组 和初始化 - dp := make([]int, bagWeight+1) - // 遍历顺序 - // j从0 开始 - for j := 0; j <= bagWeight; j++ { - for i := 0; i < len(weight); i++ { - if j >= weight[i] { - // 推导公式 - dp[j] = max(dp[j], dp[j-weight[i]]+value[i]) - } - // debug - //fmt.Println(dp) - } - } - return dp[bagWeight] -} - -func max(a, b int) int { - if a > b { - return a - } - return b -} - -func main() { - weight := []int{1, 3, 4} - price := []int{15, 20, 30} - fmt.Println(test_CompletePack1(weight, price, 4)) - fmt.Println(test_CompletePack2(weight, price, 4)) -} -``` -### Javascript: - -```Javascript -// 先遍历物品,再遍历背包容量 -function test_completePack1() { - let weight = [1, 3, 5] - let value = [15, 20, 30] - let bagWeight = 4 - let dp = new Array(bagWeight + 1).fill(0) - for(let i = 0; i <= weight.length; i++) { - for(let j = weight[i]; j <= bagWeight; j++) { - dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]) - } - } - console.log(dp) -} - -// 先遍历背包容量,再遍历物品 -function test_completePack2() { - let weight = [1, 3, 5] - let value = [15, 20, 30] - let bagWeight = 4 - let dp = new Array(bagWeight + 1).fill(0) - for(let j = 0; j <= bagWeight; j++) { - for(let i = 0; i < weight.length; i++) { - if (j >= weight[i]) { - dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]) - } - } - } - console.log(2, dp); -} -``` - -### TypeScript: - -```typescript -// 先遍历物品,再遍历背包容量 -function test_CompletePack(): void { - const weight: number[] = [1, 3, 4]; - const value: number[] = [15, 20, 30]; - const bagSize: number = 4; - const dp: number[] = new Array(bagSize + 1).fill(0); - for (let i = 0; i < weight.length; i++) { - for (let j = weight[i]; j <= bagSize; j++) { - dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]); - } - } - console.log(dp); -} -test_CompletePack(); -``` - -### Scala: - -```scala -// 先遍历物品,再遍历背包容量 -object Solution { - def test_CompletePack() { - var weight = Array[Int](1, 3, 4) - var value = Array[Int](15, 20, 30) - var baseweight = 4 - - var dp = new Array[Int](baseweight + 1) - - for (i <- 0 until weight.length) { - for (j <- weight(i) to baseweight) { - dp(j) = math.max(dp(j), dp(j - weight(i)) + value(i)) - } - } - dp(baseweight) - } -} -``` - -### Rust: - -```rust -impl Solution { - // 先遍历物品 - fn complete_pack() { - let (goods, bag_size) = (vec![(1, 15), (3, 20), (4, 30)], 4); - let mut dp = vec![0; bag_size + 1]; - for (weight, value) in goods { - for j in weight..=bag_size { - dp[j] = dp[j].max(dp[j - weight] + value); - } - } - println!("先遍历物品:{}", dp[bag_size]); - } - - // 先遍历背包 - fn complete_pack_after() { - let (goods, bag_size) = (vec![(1, 15), (3, 20), (4, 30)], 4); - let mut dp = vec![0; bag_size + 1]; - for i in 0..=bag_size { - for (weight, value) in &goods { - if i >= *weight { - dp[i] = dp[i].max(dp[i - weight] + value); + // 动态规划 + for (int i = 1; i < n; i++) { + for (int j = 0; j <= bagWeight; j++) { + if (j < weight[i]) { + dp[i][j] = dp[i - 1][j]; + } else { + dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]); } } } - println!("先遍历背包:{}", dp[bag_size]); + + System.out.println(dp[n - 1][bagWeight]); + scanner.close(); } } -#[test] -fn test_complete_pack() { - Solution::complete_pack(); - Solution::complete_pack_after(); -} ``` +### Go + +### Python + +```python +def knapsack(n, bag_weight, weight, value): + dp = [[0] * (bag_weight + 1) for _ in range(n)] + + # 初始化 + for j in range(weight[0], bag_weight + 1): + dp[0][j] = dp[0][j - weight[0]] + value[0] + + # 动态规划 + for i in range(1, n): + for j in range(bag_weight + 1): + if j < weight[i]: + dp[i][j] = dp[i - 1][j] + else: + dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]) + + return dp[n - 1][bag_weight] + +# 输入 +n, bag_weight = map(int, input().split()) +weight = [] +value = [] +for _ in range(n): + w, v = map(int, input().split()) + weight.append(w) + value.append(v) + +# 输出结果 +print(knapsack(n, bag_weight, weight, value)) + +``` + +### JavaScript + +