文章链接:二维背包问题一维背包问题416. 分割等和子集

视频链接:二维背包问题一维背包问题416. 分割等和子集

题目链接:背包问题416. 分割等和子集


背包问题理论基础


背包问题分类:

代码随想录算法训练营第三十五天| 背包问题(一、二维)、416. 分割等和子集_i++


01 背包(二维)

思路:

1.dp[i][j]数组的含义任取[0, i]的物品放入容量为 j 的背包里的最大价值;


2.递推公式:①不放物品i:dp[i][j] = dp[i - 1][j]; ②放物品 i : dp[i][j] = dp[i - 1][j - weight[i]] + val[i];

综上,递推公式是 :dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + val[i]);


3.初始化数组

dp[i][0],无论是选取哪些物品,背包价值总和一定为0。

dp[0][j],当 j < weight[0]的时候,dp[0][j] 应该是 0,因为背包容量比编号0的物品重量还小。

当j >= weight[0]时,dp[0][j] 应该是value[0],因为背包容量放足够放编号0物品。

代码随想录算法训练营第三十五天| 背包问题(一、二维)、416. 分割等和子集_i++_02


#include <bits/stdc++.h>
using namespace std;

int n, bagweight;
void solve() {
    vector<int> weight(n, 0);
    vector<int> value(n, 0);
    for (int i = 0; i < n; i++) {
        cin >> weight[i];
    }
    for (int i = 0; i < n; i++) {
        cin >> value[i];
    }
    vector<vector<int>> dp(n, vector<int>(bagweight + 1, 0));
    
    // 初始化
    // 当j大于等于weight[0]的值初始化为value[0]
    // 其余的已经在上面初始化为0
    for (int j = weight[0]; j <= bagweight; j++) {
        dp[0][j] = value[0];
    }
    
    // 递推公式
    for (int i = 1; i < n; i++) {
        for (int j = 0; j <= bagweight; j++) {
            if (j < weight[i]) 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[n - 1][bagweight] << endl;
}

int main() {
    while (cin >> n >> bagweight) {
        solve();
    }
    return 0;
}


01 背包(一维、滚动数组)

思路:

1.dp[i][j]数组的含义:dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]。

2.递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

3.初始化数组:下标为0的值为0;而对于下标不为0的值,为了让dp数组在递归公式的过程中取的最大的价值,而不被初始值覆盖,只需要将其初始值也设置为0就行了。


注意:

两个循环不能交换顺序,为什么?

如果交换了顺序,就是一直循环第一个物品了。(可以自己模拟试试)

#include <bits/stdc++.h>
using namespace std;


int main() {
    int m, n;
    cin >> m >> n;
    std::vector<int> weight(m); // 重量
    std::vector<int> value(m); // 价值
    
    for (int i = 0; i < m; i++) {
        cin >> weight[i];
    }
    for (int j = 0; j < m; j++) {
        cin >> value[j];
    }
    
    std::vector<int> dp(n + 1, 0);
    for (int i = 0; i < m; i++) { // 物品
        for (int j = n; j >= weight[i]; j--) { // 容量
        // 为什么从后向前遍历?
        // 答:倒序遍历是为了保证物品i只被放入一次!
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }
    cout << dp[n] << '\n';
    
    return 0;
}


416.分割等和子集

思路:

如何将该问题抽象成01背包问题?

背包的容量就是数组和的一半;只要能找出一个数组的和等于这个背包的容量就是返回true,否则就是false。


该背包问题中的重量和价值分别是什么?

重量和价值都是所给数组的值,也就是说二者是等价的。这也进一步说明了dp[target] = target;就是true的判定条件。


1.dp[i]数组的含义:表示容量为i时,背包里的最大价值的值。

2.递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);

3.初始化数组:都为0;


class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0;
        vector<int> dp(10001, 0);
        for (int i = 0; i < nums.size(); i++) {
            sum += nums[i];
        }

        if (sum % 2 == 1) return false;

        int target = sum / 2; // 背包容量
        // 开始01背包
        for (int i = 0; i < nums.size(); i++) { // 物品
            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;
    }
};