代码随想录算法训练营第四十一天|动态规划|01背包问题 二维、 01背包问题 一维 滚动数组、416. 分割等和子集

01背包问题 二维

文章

有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weighti,得到的价值是valuei 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大

dpij 表示从下标为0-i的物品里任意取,放进容量为j的背包,价值总和最大是多少

那么可以有两个方向推出来dpij

不放物品i:由dpi - 1j推出,即背包容量为j,里面不放物品i的最大价值,此时dpij就是dpi - 1j。(其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以背包内的价值依然和前面相同。)

放物品i:由dpi - 1j - weight\[i]推出,dpi - 1j - weight\[i] 为背包容量为j - weighti的时候不放物品i的最大价值,那么dpi - 1j - weight\[i] + valuei (物品i的价值),就是背包放物品i得到的最大价值

所以递归公式: dpij = max(dpi - 1j, dpi - 1j - weight\[i] + valuei);

cpp 复制代码
void test_2_wei_bag_problem1() {
    vector<int> weight = {1, 3, 4};
    vector<int> value = {15, 20, 30};
    int bagweight = 4;

    // 二维数组
    vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));

    // 初始化
    for (int j = weight[0]; j <= bagweight; j++) {
        dp[0][j] = value[0];
    }

    // weight数组的大小 就是物品个数
    for(int i = 1; i < weight.size(); 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[weight.size() - 1][bagweight] << endl;
}

int main() {
    test_2_wei_bag_problem1();
}

01背包问题 一维 滚动数组

文章

在使用二维数组的时候,递推公式:dpij = max(dpi - 1j, dpi - 1j - weight\[i] + valuei);

其实可以发现如果把dpi - 1那一层拷贝到dpi上,表达式完全可以是:dpij = max(dpij, dpij - weight\[i] + valuei);

与其把dpi - 1这一层拷贝到dpi上,不如只用一个一维数组了,只用dpj(一维数组,也可以理解是一个滚动数组)。

递推公式 dpj = max(dpj, dpj - weight\[i] + valuei);

题目给的价值都是正整数那么非0下标都初始化为0就可以了。

注意内层倒叙遍历:

cpp 复制代码
for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
}}

否则就是

dpij = max(dpi - 1j, dpij - weight\[i] + valuei);

注意 for循环顺序不可颠倒

总结:带着二维数组去思考

cpp 复制代码
void test_1_wei_bag_problem() {
    vector<int> weight = {1, 3, 4};
    vector<int> value = {15, 20, 30};
    int bagWeight = 4;

    // 初始化
    vector<int> dp(bagWeight + 1, 0);
    for(int i = 0; i < weight.size(); i++) { // 遍历物品
        for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }
    cout << dp[bagWeight] << endl;
}

int main() {
    test_1_wei_bag_problem();
}

416. 分割等和子集

文章讲解

给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

注意: 每个数组中的元素不会超过 100 数组的大小不会超过 200

示例 1:

输入: 1, 5, 11, 5

输出: true

解释: 数组可以分割成 1, 5, 511.

示例 2:

输入: 1, 2, 3, 5

输出: false

解释: 数组不能分割成两个元素和相等的子集.

提示:

1 <= nums.length <= 200

1 <= numsi <= 100

找到集合里能够出现 sum / 2 的子集总和,就算是可以分割成两个相同元素和子集了

体积和价值都是nums数组,容量为sum/2的背包价值最大为sum/2就可以。

cpp 复制代码
class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0;

        // dp[i]中的i表示背包内总和
        // 题目中说:每个数组中的元素不会超过 100,数组的大小不会超过 200
        // 总和不会大于20000,背包最大只需要其中一半,所以10001大小就可以了
        
        for (int i = 0; i < nums.size(); i++) {
            sum += nums[i];
        }
        // 也可以使用库函数一步求和
        // int sum = accumulate(nums.begin(), nums.end(), 0);
        if (sum % 2 == 1) return false;
        int target = sum / 2;
        vector<int> dp(target+1, 0);
        // 开始 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]);
            }
        }
        // 集合中的元素正好可以凑成总和target
        if (dp[target] == target) return true;
        return false;
    }
};
相关推荐
网站优化(SEO)专家几秒前
SEO核心算法拆解:网站排名快速提升的武林秘籍!
算法·搜索引擎·网站排名·核心算法
惊讶的猫15 分钟前
CLGSI
人工智能·算法·机器学习
ysa0510301 小时前
【板子】二分答案(最大最小?)
c++·笔记·算法·板子
科技之门1 小时前
百公里管网漏损分级定位实战方案2026
前端·人工智能·算法
木木子222 小时前
# 猜数字游戏 — HarmonyOS交互逻辑与随机算法实现
算法·游戏·华为·交互·harmonyos
stolentime2 小时前
SP8549 MAIN75 - BST again题解
c++·算法·二叉树·深度优先·图论·记忆化搜索·组合数学
stolentime3 小时前
AT_pakencamp_2020_day1_k Gcd of Sum题解
c++·算法
2601_956121973 小时前
map_计蒜客T1271 完美K倍子数组
c++·算法
编程圈子3 小时前
电机驱动开发学习19. 霍尔 BLDC 三段式 FOC 启动算法
驱动开发·学习·算法
Hello_Damon_Nikola3 小时前
伺服电机堵转检测算法详解
单片机·嵌入式硬件·算法