*46. 携带研究材料(第六期模拟笔试)
01背包问题。使用dp算法。
二维数组dp[i][j]的含义:在背包容量为 j 的情况下,从物品 0-i 中任意取得物品的最大价值。
01背包中的每个物品有两种状态:取 或 不取。
因此,在dp[i][j]赋值时会有两种情况:
- 不取第i个物品,则当前位置的最大价值等同于上一个物品位置在当前背包容量下的最大价值: d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j] = dp[i-1][j] dp[i][j]=dp[i−1][j]
- 取第i个物品,则当前位置的最大价值等同于上一个物品位置在背包容量刚好可以放下当前物品下的最大价值加上当前物品的价值: d p [ i ] [ j ] = d p [ i − 1 ] [ j − w e i g h t [ i ] ] + v a l u e [ i ] dp[i][j] = dp[i-1][j-weight[i]] + value[i] dp[i][j]=dp[i−1][j−weight[i]]+value[i]
这样就可以得出状态转移方程: d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − w e i g h t [ i ] ] + v a l u e [ i ] ) dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i]) dp[i][j]=max(dp[i−1][j],dp[i−1][j−weight[i]]+value[i])
在背包容量为0时,所有物品位置的价值都为0,因此,需要将dp[i][0]初始化为0。
二维数组
时间复杂度: O ( m ∗ n ) O(m * n) O(m∗n)
空间复杂度: O ( m ∗ n ) O(m * n) O(m∗n)
// c++
#include <bits/stdc++.h>
using namespace std;
int main(){
int m, n;
cin>>m>>n;
vector<int> weight(m,0);
vector<int> value(m,0);
for(int i=0; i<m; i++){
cin>>weight[i];
}
for(int i=0; i<m; i++){
cin>>value[i];
}
vector<vector<int>> dp(m, vector<int>(n+1, 0));
for(int j=weight[0]; j<=n; j++){
dp[0][j] = value[0];
}
for(int i=1; i<m; i++){
for(int j=1; j<=n; j++){
if(weight[i]>j) dp[i][j] = dp[i-1][j];
else{
dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]]+value[i]);
}
}
}
cout << dp[m-1][n] << endl;
return 0;
}
一维数组(滚动数组)
使用滚动数组时,背包容量需要从后向前遍历,因为从前向后会导致同一个物品被重复加多次。
时间复杂度: O ( m ∗ n ) O(m * n) O(m∗n)
空间复杂度: O ( n ) O(n) O(n)
// c++
#include <bits/stdc++.h>
using namespace std;
int main(){
int m, n;
cin>>m>>n;
// Files *files = new Files[m];
vector<int> weight(m,0);
vector<int> value(m,0);
// int x;
for(int i=0; i<m; i++){
cin>>weight[i];
// weight.emplace_back(x);
}
for(int i=0; i<m; i++){
cin>>value[i];
// value.emplace_back(x);
}
vector<int> dp(n+1, 0);
// for(int j=weight[0]; j<=n; j++){
// dp[0][j] = value[0];
// }
for(int i=0; i<m; i++){
for(int j=n; j>=weight[i]; j--){
dp[j] = max(dp[j], dp[j-weight[i]]+value[i]);
// cout<<dp[i][j]<<" ";
}
// cout<<endl;
}
cout << dp[n] << endl;
return 0;
}
416. 分割等和子集
依旧是01背包问题,使用上面的滚动数组方法来求解。
本题的主要思路是先将数组中的所有数求和,若该数组中的部分元素之和等于所有元素之和的一半则返回true。
因此数组元素之和必须是偶数才能等分,若是奇数直接返回false。
接下来就是经典的01背包求解了,nums中的元素同时作为weight和value进行求解。
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)
// c++
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = 0;
for(int i=0; i<nums.size(); i++){
sum += nums[i];
}
if(sum%2!=0) return false;
int target = sum/2;
vector<int> dp(target+1, 0);
for(int i=0; i<nums.size(); i++){
if(nums[i]==target) return true;
for(int j=target; j>=nums[i]; j--){
dp[j] = max(dp[j], dp[j-nums[i]]+nums[i]);
}
}
if(dp[target]==target) return true;
return false;
}
};