Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit c11b546

Browse files
从二维到一维 重讲完全背包
1 parent f3ddf8f commit c11b546

File tree

2 files changed

+399
-376
lines changed

2 files changed

+399
-376
lines changed
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
2+
# 完全背包-一维数组
3+
4+
本题力扣上没有原题,大家可以去[卡码网第52题](https://kamacoder.com/problempage.php?pid=1052)去练习。
5+
6+
## 算法公开课
7+
8+
**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[带你学透完全背包问题! ](https://www.bilibili.com/video/BV1uK411o7c9/),相信结合视频再看本篇题解,更有助于大家对本题的理解**
9+
10+
11+
## 思路
12+
13+
本篇我们不再做五部曲分析,核心内容 在 01背包二维 、01背包一维 和 完全背包二维 的讲解中都讲过了。
14+
15+
上一篇我们刚刚讲了完全背包二维DP数组的写法:
16+
17+
```CPP
18+
for (int i = 1; i < n; i++) { // 遍历物品
19+
for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量
20+
if (j < weight[i]) dp[i][j] = dp[i - 1][j];
21+
else dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]);
22+
}
23+
}
24+
```
25+
26+
压缩成一维DP数组,也就是将上一层拷贝到当前层。
27+
28+
将上一层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])`
29+
30+
这里有录友想,这样拷贝的话, dp[i - 1][j] 的数值会不会 覆盖了 dp[i][j] 的数值呢?
31+
32+
并不会,因为 当前层 dp[i][j] 是空的,是没有计算过的。
33+
34+
变成 `dp[i][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i])` 我们压缩成一维dp数组,去掉 i 层数维度。
35+
36+
即:`dp[j] = max(dp[j], dp[j - weight[i]] + value[i])`
37+
38+
39+
接下来我们重点讲一下遍历顺序。
40+
41+
看过这两篇的话:
42+
43+
* [01背包理论基础(二维数组)](https://programmercarl.com/背包理论基础01背包-1.html)
44+
* [01背包理论基础(一维数组)](https://programmercarl.com/背包理论基础01背包-2.html)
45+
46+
就知道了,01背包中二维dp数组的两个for遍历的先后循序是可以颠倒了,一维dp数组的两个for循环先后循序一定是先遍历物品,再遍历背包容量。
47+
48+
**在完全背包中,对于一维dp数组来说,其实两个for循环嵌套顺序是无所谓的**!
49+
50+
因为dp[j] 是根据 下标j之前所对应的dp[j]计算出来的。 只要保证下标j之前的dp[j]都是经过计算的就可以了。
51+
52+
遍历物品在外层循环,遍历背包容量在内层循环,状态如图:
53+
54+
![动态规划-完全背包1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210126104529605.jpg)
55+
56+
遍历背包容量在外层循环,遍历物品在内层循环,状态如图:
57+
58+
![动态规划-完全背包2](https://code-thinking-1253855093.file.myqcloud.com/pics/20210729234011.png)
59+
60+
看了这两个图,大家就会理解,完全背包中,两个for循环的先后循序,都不影响计算dp[j]所需要的值(这个值就是下标j之前所对应的dp[j])。
61+
62+
先遍历背包再遍历物品,代码如下:
63+
64+
```CPP
65+
for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量
66+
for(int i = 0; i < weight.size(); i++) { // 遍历物品
67+
if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
68+
}
69+
cout << endl;
70+
}
71+
```
72+
73+
先遍历物品再遍历背包:
74+
75+
```CPP
76+
for(int i = 0; i < weight.size(); i++) { // 遍历物品
77+
for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量
78+
if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
79+
}
80+
}
81+
```
82+
83+
整体代码如下:
84+
85+
```cpp
86+
#include <iostream>
87+
#include <vector>
88+
using namespace std;
89+
90+
int main() {
91+
int N, bagWeight;
92+
cin >> N >> bagWeight;
93+
vector<int> weight(N, 0);
94+
vector<int> value(N, 0);
95+
for (int i = 0; i < N; i++) {
96+
int w;
97+
int v;
98+
cin >> w >> v;
99+
weight[i] = w;
100+
value[i] = v;
101+
}
102+
103+
vector<int> dp(bagWeight + 1, 0);
104+
for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量
105+
for(int i = 0; i < weight.size(); i++) { // 遍历物品
106+
if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
107+
}
108+
}
109+
cout << dp[bagWeight] << endl;
110+
111+
return 0;
112+
}
113+
```
114+
115+
116+
117+
## 总结
118+
119+
细心的同学可能发现,**全文我说的都是对于纯完全背包问题,其for循环的先后循环是可以颠倒的!**
120+
121+
但如果题目稍稍有点变化,就会体现在遍历顺序上。
122+
123+
如果问装满背包有几种方式的话? 那么两个for循环的先后顺序就有很大区别了,而leetcode上的题目都是这种稍有变化的类型。
124+
125+
这个区别,我将在后面讲解具体leetcode题目中给大家介绍,因为这块如果不结合具题目,单纯的介绍原理估计很多同学会越看越懵!
126+
127+
别急,下一篇就是了!
128+
129+
最后,**又可以出一道面试题了,就是纯完全背包,要求先用二维dp数组实现,然后再用一维dp数组实现,最后再问,两个for循环的先后是否可以颠倒?为什么?**
130+
131+
这个简单的完全背包问题,估计就可以难住不少候选人了。
132+
133+
134+
## 其他语言版本
135+
136+
### Java:
137+
138+
```java
139+
import java.util.Scanner;
140+
141+
public class Main {
142+
public static void main(String[] args) {
143+
Scanner scanner = new Scanner(System.in);
144+
int N = scanner.nextInt();
145+
int bagWeight = scanner.nextInt();
146+
147+
int[] weight = new int[N];
148+
int[] value = new int[N];
149+
for (int i = 0; i < N; i++) {
150+
weight[i] = scanner.nextInt();
151+
value[i] = scanner.nextInt();
152+
}
153+
154+
int[] dp = new int[bagWeight + 1];
155+
156+
for (int j = 0; j <= bagWeight; j++) { // 遍历背包容量
157+
for (int i = 0; i < weight.length; i++) { // 遍历物品
158+
if (j >= weight[i]) {
159+
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
160+
}
161+
}
162+
}
163+
164+
System.out.println(dp[bagWeight]);
165+
scanner.close();
166+
}
167+
}
168+
169+
```
170+
171+
172+
173+
### Python:
174+
175+
```python
176+
def complete_knapsack(N, bag_weight, weight, value):
177+
dp = [0] * (bag_weight + 1)
178+
179+
for j in range(bag_weight + 1): # 遍历背包容量
180+
for i in range(len(weight)): # 遍历物品
181+
if j >= weight[i]:
182+
dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
183+
184+
return dp[bag_weight]
185+
186+
# 输入
187+
N, bag_weight = map(int, input().split())
188+
weight = []
189+
value = []
190+
for _ in range(N):
191+
w, v = map(int, input().split())
192+
weight.append(w)
193+
value.append(v)
194+
195+
# 输出结果
196+
print(complete_knapsack(N, bag_weight, weight, value))
197+
198+
199+
```
200+
201+
202+
### Go:
203+
204+
```go
205+
206+
```
207+
### Javascript:
208+
209+
```Javascript
210+
```
211+

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /