主要介紹0-1揹包問題,及一些leetcode題解c++
有一個揹包,他的容量爲C(Capacity)。如今有n中不一樣的物品,編號爲0…n-1,其中每一件物品的重量爲w(i),價值爲v(i)。問能夠向這個揹包中盛放哪些物品,使得在不超過揹包容量的基礎上,物品的總價值最大。面試
這類組合問題,咱們均可以使用遞歸來完成。只是咱們在其中能不能找到重疊子問題,最優子結構進而轉化成記憶化搜索或動態規劃來解決。bash
對於這個問題,咱們有兩個約束條件微信
注意這個問題要使用兩個變量來定義狀態。優化
F(n,C)考慮將n個物品放進容量爲C的揹包,使得價值最大。ui
對於F(i,c),有兩種狀況,將第i個物品加入和直接忽略第i個物品spa
F(i,C) = max{F(i-1, C), v(i) + F(i-1, C-w(i))}
複製代碼
class Knapsack01{
private:
vector<vector<int>> memo;
// 用 [0...index]的物品,填充容積爲c的揹包的最大價值
int bestValue(const vector<int> &w, const vector<int> v, int index, int c){
if( c <= 0 || index < 0 ) {
return 0;
}
if( memo[index][c] != -1 ) {
return memo[index][c];
}
int res = bestValue(w, v, index-1, c);
if( c >= w[index] ) {
res = max( res , v[index] + bestValue(w, v, index-1, c-w[index]) );
}
memo[index][c] = res;
return res;
}
public:
int knapsack01(const vector<int> &w, const vector<int> &v, int C){
assert( w.size() == v.size() && C >= 0 );
int n = w.size();
if( n == 0 || C == 0 )
return 0;
memo = vector<vector<int>>( n, vector<int>(C+1,-1));
return bestValue(w, v, n-1, C);
}
};
複製代碼
模擬一下,每一個位置都由兩個位置決定code
class Knapsack01{
public:
// 用 [0...index]的物品,填充容積爲c的揹包的最大價值
int knapsack01(const vector<int> &w, const vector<int> &v, int C){
assert( w.size() == v.size() && C >= 0 );
if ( n == 0) {
return 0;
}
int n = w.size();
vector<vector<int>> memo( n, vector<int>(C+1,0));
for ( int j = 0; j <= C; j++ ) {
memo[0][j] = ( j >= w[0] ? v[0] : 0 );
}
for ( int i = 1; i < n; i++ ) {
for ( int j = 0; j <= C; j++ ) {
// 0~i這些物品容積爲j的揹包得到的最大值
memo[i][j] = memo[i-1][j];
if( j >= w[i] ) {
memo[i][j] = max( memo[i][j], v[i] + memo[i-1][j-w[i]]);
}
}
}
return memo[n-1][C];
}
};
複製代碼
上面的0-1揹包問題的時間複雜度:O(nC),空間複雜度:O(nC)。cdn
咱們分析一下狀態轉移方程:F(i,C) = max{F(i-1, C), v(i) + F(i-1, C-w(i))}
第i行元素只依賴於第i-1行元素。理論上,只須要保持兩行元素。空間複雜度:O(2*C)=O(C)。blog
class Knapsack01{
public:
int knapsack01(const vector<int> &w, const vector<int> &v, int C){
assert( w.size() == v.size() && C >= 0 );
int n = w.size();
if( n == 0 && C == 0 )
return 0;
vector<vector<int>> memo( 2, vector<int>(C+1,0));
for( int j = 0 ; j <= C ; j ++ )
memo[0][j] = ( j >= w[0] ? v[0] : 0 );
for( int i = 1 ; i < n ; i ++ )
for( int j = 0 ; j <= C ; j ++ ){
memo[i%2][j] = memo[(i-1)%2][j];
if( j >= w[i] )
memo[i%2][j] = max( memo[i%2][j], v[i] + memo[(i-1)%2][j-w[i]]);
}
return memo[(n-1)%2][C];
}
};
複製代碼
只使用一行完成揹包問題,比較複雜:咱們從右向左刷新內容
class Knapsack01{
public:
int knapsack01(const vector<int> &w, const vector<int> &v, int C){
assert( w.size() == v.size() && C >= 0 );
int n = w.size();
if( n == 0 || C == 0 )
return 0;
vector<int> memo(C+1,0);
for( int j = 0 ; j <= C ; j ++ )
memo[j] = ( j >= w[0] ? v[0] : 0 );
for( int i = 1 ; i < n ; i ++ )
for( int j = C ; j >= w[i] ; j -- )
memo[j] = max( memo[j], v[i] + memo[j-w[i]]);
return memo[C];
}
};
複製代碼
典型的揹包問題,在n個物品中選出必定物品,填滿sum/2的揹包。
class Solution {
private:
// memo[i][c] 表示使用索引爲[0...i]的這些元素,是否能夠徹底填充一個容量爲c的揹包
// -1 表示爲未計算; 0 表示不能夠填充; 1 表示能夠填充
vector<vector<int>> memo;
// 使用nums[0...index], 是否能夠徹底填充一個容量爲sum的揹包
bool tryPartition(const vector<int> &nums, int index, int sum){
if( sum == 0 ) {
return true;
}
if( sum < 0 || index < 0 ) {
return false;
}
if( memo[index][sum] != -1 ) {
return memo[index][sum] == 1;
}
memo[index][sum] = (tryPartition(nums, index-1 , sum ) ||
tryPartition(nums, index-1 , sum - nums[index] ) ) ? 1 : 0;
return memo[index][sum] == 1;
}
public:
bool canPartition(vector<int>& nums) {
int sum = 0;
for( int i = 0 ; i < nums.size() ; i ++ ){
assert( nums[i] > 0 );
sum += nums[i];
}
if( sum%2 ) {
return false;
}
memo = vector<vector<int>>(nums.size(), vector<int>(sum/2+1,-1));
return tryPartition(nums, nums.size()-1 , sum/2 );
}
};
複製代碼
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = 0;
for( int i = 0 ; i < nums.size() ; i ++ ){
assert( nums[i] > 0 );
sum += nums[i];
}
if( sum%2 ) {
return false;
}
int n = nums.size();
int C = sum / 2;
vector<bool> memo(C+1, false);
for ( int i = 0; i <= C; i++ ) {
memo[i] = ( nums[0] == i );
}
for ( int i = 1; i < n; i++ ) {
for ( int j = C; j >= nums[i]; j-- ) {
memo[j] = memo[j] || memo[ j - nums[i] ];
}
}
return memo[C];
}
};
複製代碼
-------------------------華麗的分割線--------------------
看完的朋友能夠點個喜歡/關注,您的支持是對我最大的鼓勵。
想了解更多,歡迎關注個人微信公衆號:番茄技術小棧