52. 携带研究材料(第七期模拟笔试)
完全背包问题,每个物品可以取多次。
二维数组
时间复杂度:
O
(
n
∗
m
)
O(n*m)
O(n∗m)
空间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
tips: 二维数组的状态转移方程:
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
]
[
j
−
w
e
i
g
h
t
[
i
]
]
+
v
a
l
u
e
[
i
]
)
dp[i][j] = max(dp[i-1][j], dp[i][j-weight[i]]+value[i])
dp[i][j]=max(dp[i−1][j],dp[i][j−weight[i]]+value[i])
注意max函数里的第二项是取dp[i][]而不是dp[i-1][]。dp[i][]代表物品可以重复取,是完全背包;dp[i-1][]代表物品只能取一次,是01背包。
// c++
// 注释部分为输出dp数组
#include<bits/stdc++.h>
using namespace std;
int main(){
int N,V;
cin>>N>>V;
vector<int> weight;
vector<int> value;
vector<vector<int>> dp(N+1, vector<int>(V+1, 0));
for(int i=0; i<N; i++){
int x,y;
cin>>x>>y;
weight.push_back(x);
value.push_back(y);
}
for(int j=weight[0]; j<=V; j++){
dp[0][j] = dp[0][j-weight[0]] + value[0];
// cout<<dp[0][j]<<" ";
}
// cout<<endl;
for(int i=1; i<N; i++){
for(int j=0; j<=V; 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[i][j]<<" ";
}
// cout<<endl;
}
cout<<dp[N-1][V];
return 0;
}
一维数组(滚动数组)
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
tips: 使用滚动数组方法,01背包和完全背包的区别仅在背包的遍历顺序,01背包从后向前,确保物品只放一次;完全背包从前向后,物品可放多次。
// c++
#include<iostream>
#include<vector>
using namespace std;
int main(){
int N,V;
cin>>N>>V;
vector<int> weight;
vector<int> value;
vector<int> dp(V+1, 0);
for(int i=0; i<N; i++){
int x,y;
cin>>x>>y;
weight.push_back(x);
value.push_back(y);
}
for(int i=0; i<N; i++){
for(int j=weight[i]; j<=V; j++){
dp[j] = max(dp[j], dp[j-weight[i]]+value[i]);
}
}
cout<<dp[V];
return 0;
}
518. 零钱兑换 II
dp[i]代表amount=i时的硬币组合数,状态转移方程不太好想,需要动手写一下才能推出来。
状态转移方程:
d
p
[
j
]
+
=
d
p
[
j
−
c
o
i
n
s
[
i
]
]
dp[j] += dp[j-coins[i]]
dp[j]+=dp[j−coins[i]]
使用滚动数组,后台测试数据当amount=0时返回1,同时防止dp求和到最后都是0,所以dp[0]初始化为1。
外层遍历硬币,内层遍历背包容量。
排序可有可无,在输出dp数组查看时,排序后的结果更容易理解。
**tips:**这里需要注意内外层循环的顺序,若硬币是外层,则计算的是组合数;若背包容量是外层,则计算的是排列数。
时间复杂度:
O
(
m
∗
n
)
O(m*n)
O(m∗n)
空间复杂度:
O
(
n
)
O(n)
O(n)
// c++
class Solution {
public:
int change(int amount, vector<int>& coins) {
vector<int> dp(amount+1, 0);
// sort(coins.begin(), coins.end());
dp[0] = 1;
for(int i=0; i<coins.size(); i++){
for(int j=coins[i]; j<=amount; j++){
dp[j] += dp[j-coins[i]];
}
for(int j=0; j<=amount; j++) cout<<dp[j]<<" ";
cout<<endl;
}
return dp[amount];
}
};
377. 组合总和 Ⅳ
本题使用上题提到的排列遍历顺序,即外层遍历背包容量,内层遍历物品。当处在任一个背包容量下都会遍历所有物品。
dp[j]存储的是背包容量为j时的排列个数。测试数据里有查出int范围的数,所以需要加一个判断或者定义为unsigned int。
时间复杂度:
O
(
n
∗
m
)
O(n*m)
O(n∗m)
空间复杂度:
O
(
n
)
O(n)
O(n)
// c++
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
vector<int> dp(target+1, 0);
// sort(nums.begin(), nums.end());
// if(nums[0]>target) return 0;
dp[0] = 1;
for(int j=0; j<=target; j++){
for(int i=0; i<nums.size(); i++){
if(j>=nums[i] && dp[j]<INT_MAX-dp[j-nums[i]]) dp[j] += dp[j-nums[i]];
}
// for(int j=0; j<=target; j++) cout<<dp[j]<<" ";
// cout<<endl;
}
return dp[target];
}
};
57. 爬楼梯(第八期模拟笔试)
使用动态规划解决爬楼梯问题。和上题基本一致,依旧是全排列解法。dp[i]代表第i阶楼梯有多少种爬法。
时间复杂度:
O
(
n
∗
m
)
O(n*m)
O(n∗m)
空间复杂度:
O
(
n
)
O(n)
O(n)
// c++
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,m;
cin>>n>>m;
vector<int> dp(n+1,0);
dp[0] = 1;
for(int i=1; i<=n; i++){
for(int j=1; j<=m; j++){
if(i>=j) dp[i] += dp[i-j];
}
}
cout<<dp[n];
return 0;
}