- Backpack
中文English
Given n items with size Ai, an integer m denotes the size of a backpack. How full you can fill this backpack?
Example
Example 1:
Input: [3,4,8,5], backpack size=10
Output: 9
Example 2:
Input: [2,3,5,7], backpack size=12
Output: 12
Challenge
O(n x m) time and O(m) memory.
O(n x m) memory is also acceptable if you do not know how to optimize memory.
Notice
You can not divide any item into small pieces.
解法1:二维数组
代码如下:
class Solution {
public:
/**
* @param m: An integer m denotes the size of a backpack
* @param A: Given n items with size A[i]
* @return: The maximum size
*/
int backPack(int m, vector<int> &A) {
int n = A.size();
vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
for (int i = 1; i <= n; ++i) {
// for (int j = m; j >= A[i]; --j) {
// for (int j = A[i - 1]; j <= m; ++j) {
for (int j = 1; j <= m; ++j) { //也可以写成 for (int j = m; j >= 1; --j)
if (j >= A[i - 1]) {
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - A[i - 1]] + A[i - 1]);
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[n][m];
}
};
注意:
- 第二个for循环里面,j必须从1开始。写成
for (int j = 1; j <= m; ++j)
和
for (int j = m; j >= 1; --j)
都是可以的。
为什么写成for (int j = m; j >= 1; --j)也可以呢? 因为是两层循环,不管j是从大到小,还是从小到大,计算dp[i][j]的时候,dp[i - 1][j - A[i - 1]]肯定已经在i-1这上一轮循环里面计算过了。
但不能写成
for (int j = m; j >= A[i]; --j) {
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - A[i - 1]] + A[i - 1]);
}
也不能写成
for (int j = A[i - 1]; j <= m; ++j) {
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - A[i - 1]] + A[i - 1]);
}
因为dp[i][0 … A[i]]这段一直都是0,而本来这前i个物品在包的大小在0…A[i]之间是可以占很多位置的。
2) for i和for j两个循环位置可以互换。
3) 第一个循环不能反过来,第二个循环可以反过来。
上面的代码也可以写成
class Solution {
public:
/**
* @param m: An integer m denotes the size of a backpack
* @param A: Given n items with size A[i]
* @return: The maximum size
*/
int backPack(int m, vector<int> &A) {
int n = A.size();
vector<vector<int>> dp(n + 1, vector<int>(m + 1));
for (int i = 1; i <= n; ++i) {
for (int j = m; j >= 0; --j) {
dp[i][j] = dp[i - 1][j];
if (j >= A[i - 1]) {
dp[i][j] = max(dp[i][j], dp[i - 1][j - A[i - 1]] + A[i - 1]);
}
}
}
return dp[n][m];
}
};
解法2:用一维数组优化
代码如下:
class Solution {
public:
/**
* @param m: An integer m denotes the size of a backpack
* @param A: Given n items with size A[i]
* @return: The maximum size
*/
int backPack(int m, vector<int> &A) {
int n = A.size();
vector<int> dp(m + 1, 0);
for (int i = 0; i < n; ++i) {
for (int k = m; k >= A[i]; --k) {
//for (int k = A[i]; k <= m; ++k) { //wrong!!!
dp[k] = max(dp[k], dp[k - A[i]] + A[i]);
}
}
return dp[m];
}
};
注意:
1)第二个for循环不能写成for (int k = A[i]; k <= m; ++k)。为什么呢?因为
里面的dp[k] = max(dp[k], dp[k - A[i]] + A[i])实际上相当于
dp[i][k] = max(dp[i - 1][k], dp[i - 1][k - A[i]] + A[i])
如果k是从大到小的话,那么dp[k]计算的时候dp[k - A[i]]确实是上一轮的dp[k - A[i]]。
但是如果k是从小到大的话,那么dp[k]计算的时候dp[k - A[i]]就是本轮的dp[k - A[i]]。那么结果就不对了。
也就是说,dp[k]和dp[k-A[i]],k>k-A[i],我们要从大到小循环,才能保证dp[k]计算的时候dp[k - A[i]]确实是上一轮的dp[k - A[i]]。
2)上面的写法实际上等价于
int backPack(int m, vector<int> &A) {
int n = A.size();
vector<int> dp(m + 1, 0);
for (int i = 0; i < n; ++i) {
for (int k = m; k >= 0; --k) {
if (k >= A[i])
dp[k] = max(dp[k], dp[k - A[i]] + A[i]);
else
dp[k] = dp[k];
}
}
return dp[m];
}
但dp[k]=dp[k]纯属多余,第2个循环可以简化成
for (int k = m; k >= A[i]; --k) {
dp[k] = max(dp[k], dp[k - A[i]] + A[i]);
注意:为什么这类背包问题不能用贪婪算法?因为我们不能只取一部分物品,必须整个取或者不取。如果我们可以取物品的一部分,比如说20斤豆子,我们取其中8斤,这样的题目就可以用贪婪算法。
解法3:用回溯+memorization
class Solution {
public:
/**
* @param m: An integer m denotes the size of a backpack
* @param a: Given n items with size A[i]
* @return: The maximum size
*/
int backPack(int m, vector<int> &a) {
if (a.size() == 0) return true;
if (m == 0) return false;
int n = a.size();
mem.resize(n + 1, vector<bool> (m + 1, false)); //memorization
helper(0, 0, m, a);
return g_sum;
}
private:
void helper(int index, int sum, int m, const vector<int> &a) {
int n = a.size();
if (mem[index][sum]) return;
if (sum > g_sum) g_sum = sum;
if (index >= n) return;
mem[index][sum] = true;
helper(index + 1, sum, m, a); //do not pick up the index th item
if (m >= sum + a[index]) { //pick up the index th item
helper(index + 1, sum + a[index], m, a);
}
}
int g_sum = 0;
vector<vector<bool>> mem;
};
helper()写成这样也可以。
void helper(int index, int sum, int m, const vector<int> &a) {
int n = a.size();
if (sum > g_sum) g_sum = sum;
if (index >= n) return;
if (mem[index][sum]) return;
mem[index][sum] = true;
helper(index + 1, sum, m, a); //do not pick up the index th item
if (m >= a[index]) { //pick up the index th item
helper(index + 1, sum + a[index], m - a[index], a);
}
}