代码随想录算法训练营第四十二天|01背包问题,416. 分割等和子集

news2024/11/16 13:01:23

背包问题

背包问题一般有以下几类:
在这里插入图片描述
掌握01背包和完全背包即可。
先理解01背包。完全背包可以看作是01背包问题的变形。

01背包

什么是01背包问题?

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

举这样一个例子:
背包的最大重量为4。
物品为:
在这里插入图片描述
问:背包能背的物品最大价值是多少?

01背包 二维

我们依旧使用动态规划的五部曲来分析。
1、确定dp数组以及下标的含义。

定义一个dp[i][j]

这里直接写了dp[i][j],不明白为什么能这么写?是怎么想出来要这么定义的?为什么会有一个j?

该数组的含义是什么?
含义:任取下标为[0,i]之间的物品放入容量为j的背包里

或者可以理解为:
dp[i][j]表示背包容量为 j 时,从下标0至i的物品中选取,可以获得的最大价值。

在这里插入图片描述
2、确定递推公式
我们要思考,dp[i][j]这个结果可以从哪里得到?
背包的状态取决于放不放物品i。
对于任意一个物品,都只有两种状态,放和不放,物品i同样如此。

(1)不放物品i
不放物品i,从前i-1个物品中就得到了最优解。
即:背包容量为 j 时,从下标0至i-1的物品中选取,就能获得最大价值。
此时结果为:dp[i-1][j]

(2)放物品i
先写出表达式:
dp[i-1][j-weight[i]]+value[i]
其中,weights[i]表示第i个物品的重量,value[i]是第i个物品的价值。

我们现在要放物品i。因为要放物品i,那就不需要再遍历到i了。因为i已经确定要放入了,相当于一个前提条件,只需要从剩下的i-1个物品中再选即可,所以不需要遍历到i,只需要遍历到i-1。即任取物品的范围为[0, i-1]。

这种情况下物品i已经放入了背包中,背包的容量也要发生变化。此时我们要求的应该是已经放入物品i之后,剩余的重量还能放多少。因此背包的重量为j-weight[i]。
就有表达式:dp[i-1][j-weight[i]]
含义是:在背包容量为j-weight[i]的情况下,从下标为[0,i-1]的物品中任意选取,得到的最大价值。

dp[i][j]是从0至i的物品中选取,现在物品i已经放进去了,就要包括物品i的价值,因此要加上物品i的价值。

综上分析,就可以得到表达式:
dp[i-1][j-weight[i]]+value[i]

针对于情况(1)和情况(2),我们最终求的结果是最大的价值,因此最终的结果应该是两种情况中取得的最大值,谁的结果大就选哪一种情况。

因此递推公式为:
dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i])

3、初始化dp数组
对于这一部分,我是看了另外一个博主的文章讲解,这里就不写了。直接去看该博主的原文即可。

参考链接: https://www.cnblogs.com/DAYceng/p/17258797.html

4、确定遍历顺序
有两个遍历的维度:物品与背包重量。
先遍历谁都行。
先遍历物品再遍历背包重量更简单。
(1)先遍历物品再遍历背包
方向就是从左向右
在这里插入图片描述
固定物品0,去遍历背包,看看能不能放下?最大价值是多少。
只有背包容量为0的时候放不下,最大价值为0;背包容量1,2,3,4的时候都能放下物品0,最大价值均为15;

再固定物品1,去遍历背包。背包容量为0,1,2的时候放不下物品1,最大价值不变。背包容量为3,物品1可以替换原来的物品0,最大价值由原来的15变成了20。背包容量为4,这个时候物品0和物品1都可以放下,最大价值就更新为20+15=35。

其他位置的遍历分析同理。

(2)先遍历背包再遍历物品
方向就是从上到下
在这里插入图片描述
固定背包容量为0。所有的物品都装不下,最大价值为0。

固定背包容量为1。物品0可以装下,物品1和物品2都装不下,因此背包容量为1的时候最大价值为15。

固定背包容量为2。物品0可以装下,物品1和物品2都装不下,因此背包容量为1的时候最大价值为15。

固定背包容量为3。遍历到物品0,可以装下,此时最大价值为15;再遍历到物品1,发现物品1可以装下,就把物品0替换为物品1,最大价值也由15变成了20;遍历到物品2,无法装下,此时的最大价值还是20.

其他位置的遍历分析同理。

5、举例推导dp数组
如下图所示:
在这里插入图片描述
最优解(最大价值)是dp[2][4]。

代码实现

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背包 一维滚动数组

动态规划五部曲。
1、确定dp数组以及下标的含义。
d[j]:容量为j的背包,所背的物品的最大价值为dp[j]

2、确定递归公式
与二维时的情况类似,也分为放入物品i和不放入物品i这两种情况。
(1)不放入物品i
二维表达式为:dp[i-1][j]

一维表达式:dp[j]
dp[j]就还是取上一层自身的值

(2)放入物品i
二维表达式:
dp[i-1][j-weight[i]]+value[i]
一维表达式:
d[j-weight[i]]+value[i]

综上,递推公式为:
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
相对于二维dp数组的写法,就是把dp[i][j]中i的维度去掉了。

3、初始化dp数组
背包容量为0,则背包内的最大价值为0。
即dp[0]=0

其他位置如何初始化?
根据递推公式,我们总是去取最大值。因此,如果题目给的物品值均为正数,那dp[0]以外的位置应该初始化为0,这样才可以保证在递推过程中,判断累加所得的最大值不会被初始值覆盖

举个例子,当j移动到5处时,即dp[5],如果此前所背物品价值累加为10,而当前dp[5]的初始值是100,就会把之前的值覆盖掉。

所以,在创建dp数组的时候,把所有的元素都初始化为0就行。

4、确定遍历顺序
一维的遍历顺序和二维的有很大的区别。

一维的遍历,需要先遍历物品再遍历背包,同时遍历背包需要倒序遍历。

为什么需要倒序遍历?

倒序遍历是为了保证物品只被放入了一次。

比如文章开头的例子:
在这里插入图片描述
物品0的重量为weight[0] = 1,价值value[0] = 15
如果使用正序遍历:
dp[0] = 0;---初始化是0
dp[1] = dp[1 - weight[0]] + value[0] = 15;
dp[2] = dp[2 - weight[0]] + value[0] = 30;

当j为1时,表示容量为1,此时能够放下一个物品0,根据递推公式我们应该让 dp[j]等于dp[j - weight[i]] + value[i],即需要放入物品,因此有了上述式子。

当j为2时,容量为2,根据递推公式此时确实需要放入物品,因为当前层容量够。
但是,由于遍历顺序是正序遍历,在计算dp[2]时会把dp[1]的结果累加进来,这显然是错误的,因为每个物品只能放一次。
所以正序遍历有问题。

如果使用倒序遍历:
dp[2] = dp[2 - weight[0]] + value[0] = 15;
dp[1] = dp[1 - weight[0]] + value[0] = 15;
dp[0] = 0;---初始化是0
结果是正常的。

个人理解是后面的结果需要前面的原值来更新,后序遍历可以保证前面的值不变,前序遍历会让前面的值变。

5、举例推导dp数组
如下图所示:
在这里插入图片描述

代码实现

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. 分割等和子集

题目描述

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

在这里插入图片描述

题目链接/讲解链接:
https://programmercarl.com/0416.%E5%88%86%E5%89%B2%E7%AD%89%E5%92%8C%E5%AD%90%E9%9B%86.html

思路

在这里插入图片描述

解题

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0;

        // dp[i]中的i表示背包内总和
        // 题目中说:每个数组中的元素不会超过 100,数组的大小不会超过 200
        // 总和不会大于20000,背包最大只需要其中一半,所以10001大小就可以了
        vector<int> dp(10001, 0);
        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;

        // 开始 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;
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1620161.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用Gitee进行社交登录的流程

使用Gitee进行社交登录 创建Gitee第三方应用流程&#xff1a; 鼠标移动到个人头像上&#xff0c;点击账号设置 点击账号设置&#xff0c;选择左边目录下数据管理的第三方应用 然后选择创建应用 根据要求填写 填写好了上面的要求之后&#xff0c;点击创建应用&#xff0c;这样&…

孙中亮:北斗三十周年,看北斗芯片高质量发展历程和方向

1994年1月10日&#xff0c;北斗一号建设正式启动&#xff0c;党中央决策建设独立自主的北斗卫星导航系统。2020年7月31日&#xff0c;北斗三号全球卫星导航系统正式开通&#xff0c;标志着北斗系统进入全球化发展新阶段。随着2024年的到来&#xff0c;北斗系统建设已走过栉风沐…

【力扣 Hot100 | 第五天】4.20(回文链表)

1.回文链表 1.1题目 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例一&#xff1a; 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;true示例二&#xff1a; 输入…

HOOPS Commuicator:基于Web的交互式2D/3D图形轻量化引擎

在当前数字化时代&#xff0c;Web基础的3D应用程序正在成为行业标准&#xff0c;尤其是在工程和制造领域。Tech Soft 3D公司旗下的HOOPS Communicator正是针对这一需求设计的高级解决方案&#xff0c;提供了一套全面的工具&#xff0c;旨在帮助开发者构建复杂的3D工程应用程序。…

【学习笔记二十五】EWM PPF自动WT后台配置和前台展示

一、概述 SAP EWM(Extended Warehouse Management)模块中的PPF(Post Processing Framework)是一个用于执行通用功能和流程的工具。PPF为SAP EWM提供了一个统一的接口,用于触发各种动作,例如打印托盘标签、交货单、拣选票或发送消息和传真。这些动作在特定条件满足时生成,…

Pasta:HHE Optimized Stream Cipher

参考文献&#xff1a; [Dae95] Daemen J .Cipher and hash function design strategies based on linear and differential cryptanalysis[J].Doctoral Dissertation K.u.leuven, 1995.[GPP11] Guo J, Peyrin T, Poschmann A. The PHOTON family of lightweight hash function…

【笔试】03

FLOPS FLOPS 是 Floating Point Operations Per Second 的缩写&#xff0c;意为每秒浮点运算次数。它是衡量计算机性能的指标&#xff0c;特别是用于衡量计算机每秒能够执行多少浮点运算。在高性能计算领域&#xff0c;FLOPS 被广泛用来评估超级计算机、CPU、GPU 和其他处理器…

日志分析简单总结

1、分析日志的目的 误报&#xff1a;不是攻击而上报成攻击 漏报&#xff1a;是攻击而没有防御的情况 日志分析可以判断是否误判或者漏判&#xff0c;可以溯源攻击行为 在护网作为防守方必备的技能&#xff08;分析NGAF和态势感知&#xff0c;发现异常&#xff09; 2、攻击出现…

芯科科技大大简化面向无电池物联网的能量采集产品的开发

芯科科技推出其迄今最高能量效率且支持能量采集功能的无线SoC 中国&#xff0c;北京 – 2024年4月22日 – 致力于以安全、智能无线连接技术&#xff0c;建立更互联世界的全球领导厂商Silicon Labs&#xff08;亦称“芯科科技”&#xff0c;NASDAQ&#xff1a;SLAB&#xff09;…

一个联合均值与方差模型的R包——dglm

目录 一、引言二、包的安装与载入三、模拟例子3.1 数据生成3.2 数据查看3.3 模型估计参数 一、引言 在 R 语言中&#xff0c;dglm 包是用于拟合双参数广义线性模型&#xff08;Double Generalized Linear Models&#xff0c;简称 DGLMs&#xff09;的一个工具。这类模型允许同…

fakak详解(2)

Kafka和Flume整合 Kafka与flume整合流程 Kafka整合flume流程图 flume主要是做日志数据(离线或实时)地采集。 图-21 数据处理 图-21显示的是flume采集完毕数据之后&#xff0c;进行的离线处理和实时处理两条业务线&#xff0c;现在再来学习flume和kafka的整合处理。 配置fl…

centos7使用源码安装方式redis

安装编译源码的工具gcc yum install -y gcc下载源码 源码下载地址 https://download.redis.io/releases/ 注意事项 不建议安装最新版本redis&#xff0c;所以我这里选择6.2.6版本 下载 wget https://download.redis.io/releases/redis-6.2.6.tar.gz解压 tar -zxvf redis-…

基于表面法线法的二维人脸图构建三维人脸模型matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ............................................................................for j 1 : …

Python基础进阶语法

目录&#xff1a; 一、基础语法二、进阶语法 一、基础语法 二、进阶语法 1、列表推导式运用 解析&#xff1a;先循环1到10内的数字&#xff0c;然后过滤大于5的数&#xff0c;赋值到new_list数组中进行打印结果。

数字信号处理操作教程_音频解码:3-8 G711A音频解码实验

一、实验目的 学习G711音频的格式和G711A音频解码的原理&#xff0c;并实现将BIT格式解码为PCM格式。 二、实验原理 G711 G711是国际电信联盟订定出来的一套语音压缩标准&#xff0c;主要用于电话。它主要用脉冲编码调制对音频采样&#xff0c;采样率为8k每秒。它利用一个 …

VUE2版本的仿微信通讯录侧滑列表

<template><!-- Vue模板部分 --><div><div v-for"(group, index) in groupedArray" :key"index" ref"indexcatch"><h2>{{ letter[index] }}</h2><ul><li v-for"item in group" :key&quo…

什么因素可以影响到代理IP稳定性?爬虫代理IP有哪些作用?

一、什么因素可以影响到代理IP稳定性 代理IP的稳定性受到多种因素的影响&#xff0c;以下是一些主要的因素&#xff1a; 代理IP的质量&#xff1a;不同的代理IP提供商提供的代理IP质量参差不齐&#xff0c;一些低质量的代理IP可能经常出现连接问题或速度慢的情况&#xff0c;…

Day:动态规划 LeedCode 123.买卖股票的最佳时机III 188.买卖股票的最佳时机IV

123. 买卖股票的最佳时机 III 给定一个数组&#xff0c;它的第 i 个元素是一支给定的股票在第 i 天的价格。 设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。 注意&#xff1a;你不能同时参与多笔交易&#xff08;你必须在再次购买前出售掉之前的股票&a…

解码数据世界:统计学入门与应用指南

引言 统计学可以被定义为研究数据的科学&#xff0c;它涉及到数据的收集、分析、解释和呈现。其目标是从数据中提取有意义的信息&#xff0c;并使用这些信息来做出推断与决策。 统计学主要分别以下几个主要领域&#xff1a; 描述性统计&#xff1a;使用图表、图形和其他工具…

C++之入门

文章目录 1、前言2、C的关键字2.1C语言32关键字2.2C关键字(63个) 3、命名空间4、输入输出(cout、cin)4、缺省参数5、函数重载6 引用6.1 引用的定义6.2 引用的特性6.3引用的使用场景6.4 实际例子6.5、总结 7、内联函数8、auto关键字9、nullptr关键字 1、前言 C语言是结构化和模…