LintCode 92: Backpack (经典背包DP题)

  1. 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];
    }
};

注意:

  1. 第二个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);
        }
    }

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值