LeetCode刷题笔记【32】:动态规划专题-4(二维背包问题、一维背包问题、分割等和子集)

news2025/3/1 16:11:04

文章目录

  • 动态规划前置知识
  • 背包问题前置知识
    • 什么是背包问题, 背包问题举例
    • 背包问题的大致分类
      • 01背包
      • 完全背包
    • 背包问题的通用解法
  • 二维背包问题
    • 题目描述
    • 解题思路
      • 1 构建dp数组
      • 2 初始化dp数组
      • 3 遍历更新dp数组
    • 代码
  • 一维背包问题
    • 题目描述
    • 解题思路
    • 代码
  • 416. 分割等和子集
    • 题目描述
    • 解题思路
    • 代码
  • 总结

动态规划前置知识

参考前文

参考文章:
LeetCode刷题笔记【29】:动态规划专题-1(斐波那契数、爬楼梯、使用最小花费爬楼梯)
LeetCode刷题笔记【30】:动态规划专题-2(不同路径、不同路径 II)
LeetCode刷题笔记【31】:动态规划专题-3(整数拆分、不同的二叉搜索树)

背包问题前置知识

什么是背包问题, 背包问题举例

背包问题的典型案例如下:

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

以上是对背包问题的直白描述, 但是在实际刷题过程中大概率不会这样出题(甚至LeetCode官方也没有"背包问题"这道题)
所以实际应用中, 需要将一般问题转化为背包问题来求解(如今天的第三道例题)

背包问题的大致分类

在这里插入图片描述
如上图所示, 详细的背包问题分类大概会有 01背包, 完全背包, 多重背包, 分组背包.
但是实际面试中, 最多就考到01背包和完全背包, 更多的就是竞赛水平的题目了.

01背包

就像上面的那个举例, 每一个物品只且只有一个;

所以对于每个物品而言, 只有不放进背包(0)放进背包(1)两种状态, 所以是"01背包"

完全背包

完全背包问题由01背包问题转化而来, 与之不同的是, 完全背包问题中, 每个物品的数量是无限的

背包问题的通用解法

虽然也可以用回溯遍历的方法, 尝试每个物品"放入/不放入"的所有情况, 但那样的时间复杂度就是指数级的;
故背包问题的最优解法是动态规划算法;

所以说: 背包问题的本质还是动态规划问题
今天三道例题的前两道就将使用类似的动态规划思路, 解决典型的01背包问题.

二维背包问题

题目描述

在这里插入图片描述

解题思路

1 构建dp数组

在这里插入图片描述
既然是动态规划, 那么我们就来构建一个如上图所示的二维dp数组;

vector<vector<int> dp(物品数量, vector<int>(背包容量+1, 0))

dp数组中每一格的意义是:
dp[i][j]表示: 在面对容量为j的背包, 以及物品0~i时, 我所能获得的最大收益

2 初始化dp数组

在这里插入图片描述
那么首先可以理解上图中为什么这样进行dp数组的初始化, 因为当j太小, 放不下物品0, 收益为0, 放得下以后, 收益为value[0];
代码为:

for(int j=物品0的重量; j<背包容量; ++j){
	dp[0][j] = 物品0的收益;
}

而后进行dp数组的遍历更新

3 遍历更新dp数组

在这里插入图片描述
我们以对物品1这一行的更新为例, 对dp[1][0], dp[1][1], dp[1][2]的值应该没什么异议, 因为此时j<3, 还没有到物品1的重量(3), 所以只能照抄上一行的数值(即只放入物品0)

关键是对dp[1][3]dp[1][4], 也就是说, 在背包的容量可以容纳物品1后, 我们怎么做出选择, 来让当前收益最大.
也就是如何拟定递推公式

符合直觉的思路是: 在放入物品1和不放入物品1中选max, 即max(不放入物品1的收益, 放入物品1的收益)
不放入物品1的收益, 很明显就是上一行的值照抄, 即dp[0][j]
放入物品1的收益就复杂一些, 因为要考虑到放入物品1后, 剩余的背包空间如何使用才能获得最大收益

(这动态规划的小味儿是不是挠儿的一下就上来了~~)

很明显, 放入物品1后的剩余空间的最大收益, 就是上一行中的dp[0][j-weight[1]]
所以放入物品1的收益=物品1本身的收益+剩余空间的收益, 就是dp[1][j] = value[1] + dp[0][j-weight[1]]

把上面这一系列推导综合一下, 并且从物品1推导到物品i, 就可得到以下代码:

for(int i=1; i<物品数量; ++i){//遍历除了物品0以外的其他物品
	for(int j=0; j<=背包容量; ++j){//遍历所有背包容量的情况
		if(j<weight[i]{//如果背包大小都放不下物品i
			dp[i][j] = dp[i-1][j];//那么就只能照抄上一行
		}else{//如果背包的大小放得下物品i
			dp[i][j] = max(dp[i-1][j], value[i]+dp[i-1][j-weight[i]);//权衡一下不放/放物品i的收益
			// 放物品i的收益 = 物品i本身的收益 + 放物品i后剩余空间所能产生的最大收益
		}
	}
}

代码

int main()
{
    int bagSize = 4;
    vector<int> weight = { 1, 3, 4 };
    vector<int> value = { 15, 20, 30 };
    vector<vector<int>> dp(weight.size(), vector<int>(bagSize + 1, 0));
    // 初始化
    for (int j = 0; j <= bagSize; ++j) {
        if (j >= weight[0]) {
            dp[0][j] = value[0];
        }
    }
    // 遍历
    for (int i = 1; i < weight.size(); ++i) {
        for (int j = 1; j <= bagSize; ++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.back().back();
}

一维背包问题

题目描述

截图
还是和第一题一样的题目, 但是我们这次使用一维dp数组, 用更低的空间复杂度来解题.

解题思路

在这里插入图片描述
从上图中我们可以看出: 更新遍历的过程, 就是参照上一行的内容(严格来说是上一行左上方的内容), 来填充当前行中内容的过程.

那我们其实不用我维护一整个二维dp数组, 只需要维持原先二维dp数组中的一行(一维dp数组(滚动数组)), 然后更新其中的内容就行了.

那么递推公式就是: dp[j] = max(dp[j], value[i] + dp[j-weight[i]])
代码如下:

for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = bagSize; j >= weight[i]; j--) { // 遍历背包容量
        dp[j] = max(dp[j], value[i] + dp[j - weight[i]]);
    }
}

我们发现: 这个遍历更新的过程, 和刚才二维dp数组中, 初始化的过程基本一样, 那我们可不可以将初始化和遍历更新过程合并起来呢?
是可以的, 但是要注意遍历每一行的顺序是反的(即从bagSizeweight[j]), 因为如果从weight[j]bagSize遍历, 会出现对物品0多次使用的问题, 而题目要求每个物品只能使用一次.

代码

// 以上使用二维的动态规划dp数组解决背包问题
// 但是可以发现: 遍历更新dp数组的过程, 其实就是"根据上一行的内容生成这一行的内容"
// 所以本质上可以使用一维dp数组(一行), 来实现遍历过程
int main()
{
    // 基本信息
    int bagSize = 4;
    vector<int> weight = { 1, 3, 4 };
    vector<int> value = { 15, 20, 30 };
    // 初始化
    vector<int> dp(bagSize + 1, 0);
    // 遍历
    for (int i = 0; i < weight.size(); ++i) {
        for (int j = bagSize; j >= weight[i]; --j) {
            dp[j] = max(dp[j], value[i] + dp[j - weight[i]]);
        }
    }
    cout << dp.back();
}
// 和之前的二维dp数组相比, 一维dp数组的解法中, 一方面遍历顺序是从后往前遍历(避免重复处理的问题);
// 另一方面不需要单独初始化第一行了, 可以在遍历过程中初始化

416. 分割等和子集

题目描述

截图

LeetCode链接:https://leetcode.cn/problems/partition-equal-subset-sum/description/

解题思路

本身题目的描述, 相当于让我们挑出两组数, 让每组的和相等, 如果用回溯暴力解题是很复杂的, 时间复杂度也会很高.

可以将其转化为背包问题:
先求所有数字num的和sum, 将sum/2作为target;
因为每个数字都只能使用一次, 那么问题就转化为01背包问题, 即bagSize=target, 物品iweight[i]value[i]都是nums[i], 能否得到一种组合, 可以正好装满容量为target的背包 (即bagSize=target时, 最大收益为target).

代码

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum=0;
        for(int num : nums)
            sum += num;
        if(sum % 2 == 1)
            return false;
        target = sum/2;
        vector<int> dp(target  + 1, 0);
        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;
        else
            return false;
    }
};

总结

这一篇笔记可以说是最近写的最详细认真的一篇了, 背包问题的思路确实十分巧妙;
在掌握了背包问题本身的套路以后, 难点或许就是"如何将其他问题转化为背包问题";
这就要在后续的学习中进一步磨炼了.

本文参考:
二维背包问题
一维背包问题
分割等和子集

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

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

相关文章

Linux设备树详细学习笔记

参考文献 参考视频 开发板及程序 原子mini 设备树官方文档 设备树的基本概念 DT:Device Tree //设备树 FDT: Flattened Device Tree //开放设备树&#xff0c;起源于OpenFirmware (所以后续会见到很多OF开头函数) dts: device tree source的缩写 //设备树源码 dtsi: device …

Vuex核心 - 模块 module(进阶)创建拆分

文章目录 module分模块一、什么是 模块module二、module的好处三、模块创建-拆分 module分模块 一、什么是 模块module 在Vuex中&#xff0c;模块&#xff08;module&#xff09;是用来组织和管理状态&#xff08;state&#xff09;、行为&#xff08;actions&#xff09;、变…

CIO40--数字化转型之回报效益ROI(含表格)

一﹑对BOSS的好处 随时可以由系统中的资料掌握公司的营运状况。建立公司的管理体系及运作规范﹐由系统管理公司运作。建立公司营运的数据库﹐累积公司的管理经验知识﹐不会因人员异动而流失。由系统信息的整合﹐可以提升公司的反应速度﹐不需由人力统计﹐可减少错误﹐节省人力…

3D视觉测量:形位公差 GDT同轴度(附源码)

文章目录 0. 测试效果1. 基本内容2. 公共轴线法3. 实现代码(待添加)目标:通过3D视觉的方法测量圆柱的同轴度 0. 测试效果 1. 基本内容 "同轴度" 是一个工程学和制造业中常用的术语,用来描述一个对象、零件或装置的各个组成部分是否在一个共同的轴线上对齐…

自动驾驶之高精地图介绍

高精地图 文章目录 高精地图前言一、什么是高精地图 前言 一、什么是高精地图 高精地图(High Definitation Map,HD MAP)&#xff0c;和普通导航电子地图的主要区别是精度更高、信息更丰富。精度更高主要体现在高精地图的绝对坐标精度更高(指的是地图上某个目标和外部的真实世…

linux 网络接口的子接口的配置

参考&#xff1a; https://blog.csdn.net/baidu_38803985/article/details/104653205 在 Linux 中&#xff0c;网络接口通常以ethX的形式命名&#xff0c;其中X代表接口的编号&#xff0c;例如eth0代表第一个网络接口&#xff0c;eth1代表第二个&#xff0c;依此类推。虚拟子接…

嵌入式学习笔记(22)汇编实现时钟设置代码详解

4.6.1时钟设置的步骤分析 第1步&#xff1a;先选择不使用PLL。让外部24MHz原始时钟直接过去&#xff0c;绕过APLL那条路。 第2步&#xff1a;设置锁定时间&#xff08;PLL_LOCK&#xff09;。默认值是0x0FFF&#xff0c;保险起见我们设置0xFFFF 第3步&#xff1a;设置分频系…

Linux 服务器连接方式

这里服务器使用 Ubuntu 20.04.6 LTS aarch64&#xff0c;这篇文章就不说使用工具连接了&#xff0c;工具直接添加就好了&#xff0c;这里说下终端命令操作 SSH 命令使用密码连接 使用以下命令在终端进行密码连接 ssh usernamehostname如果是第一次连接 SSH 客户端会提示你是否…

盘点一个os.path.join()函数遇到的小问题(文末赠书)

点击上方“Python爬虫与数据挖掘”&#xff0c;进行关注 回复“书籍”即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 连峰去天不盈尺&#xff0c;枯松倒挂倚绝壁。 大家好&#xff0c;我是皮皮。 一、前言 前几天在Python最强王者群【小马哥】问了一个os路径拼接处理的问…

Kafka入门,这一篇就够了(安装,topic,生产者,消费者)

目录 Kafka的安装文件与配置目录binconfig 配置文件server.propertiesproducer.propertiesconsumer.properties 命令行简单使用kafka-topics.sh新增查看列表查看详情修改删除 kafka-console-producer.shkafka-console-consumer.sh 概念集群代理broker主题topic分区partition偏移…

用递归实现字符串逆序(不使用库函数)

文章目录 前言一、题目要求二、解题步骤1.大概框架2.如何反向排列&#xff1f;3.模拟实现strlen4.实现反向排列5.递归实现反向排列 总结 前言 嗨&#xff0c;亲爱的读者们&#xff01;我是艾老虎尤&#xff0c;今天&#xff0c;我们将探索一个题目&#xff0c;这个题目对新手非…

【计算机基础】揭露办公软件WPS、Offfice好用但又很少去做的便捷操作

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

【memcpy函数的介绍与使用和模拟实现】

memcpy函数的介绍与使用和模拟实现 1.memcpy函数的介绍 资源来源于cplusplus网站 它的作用是&#xff1a; 将数字字节的值从源指向的位置直接复制到目标指向的内存块。 源指针和目标指针指向的对象的基础类型与此函数无关; 结果是数据的二进制副本。 该函数不检查源代码中是否…

uboot顶层Makefile前期所做工作说明四

一. uboot顶层 Makefile文件 uboot 顶层 Makefile&#xff0c;就是 uboot源码工程的根目录下的 Makefile文件。 本文继续对 uboot顶层 Makefile的前期准备工作进行介绍。续上一篇文章内容的学习&#xff0c;如下&#xff1a; uboot顶层Makefile前期所做工作说明三_凌肖战的博…

信息系统项目管理师(第四版)教材精读思维导图-第十二章项目质量管理

请参阅我的另一篇文章&#xff0c;综合介绍软考高项&#xff1a; 信息系统项目管理师&#xff08;软考高项&#xff09;备考总结_计算机技术与软件专业技术_铭记北宸的博客-CSDN博客 本章思维导图源文件 ​ 12.1 管理基础 12.2 管理过程 12.3 规划质量管理 12.4 管理质量 12.5…

增强 CAD Exchanger SDK 中 B-rep 表示的渲染性能

增强 CAD Exchanger 中 B-rep 表示的渲染性能 在这篇博文中&#xff0c;我们将深入探讨增强 CAD Exchanger 产品中 B-rep 表示的渲染性能的主题&#xff0c;探讨此过程中面临的挑战&#xff0c;并讨论 CAD Exchanger 所采用的创新技术来优化它。 在 版本 3.20中&#xff0c;我…

第7篇 vue的模块化与label的转换

一 label的转换 1.1 label的转换 二 模块化 2.1 模块化 前端中&#xff0c;js文件调用js文件&#xff0c;js文件之间的调用&#xff0c;即就是模块化。 2.2 案例1 1.新建工程并初始化 2. 编写脚本 1.js // 定义成员&#xff1a; const sum function(a,b){return parseIn…

持安零信任加入PKS体系生态联盟,共创办公安全新生态

近日&#xff0c;PKS体系生态联盟公布最新一期会员单位名单&#xff0c;零信任办公安全领域的明星企业持安科技成为其网络安全领域新增会员&#xff0c;未来将与众多合作伙伴一同建设网络安全强国。 PKS体系生态联盟是在中国电子信息产业集团有限公司的倡议下&#xff0c;广泛联…

Redis数据库安装、使用、数据类型、常用命令(详解)

安装 Releases tporadowski/redis GitHub 直接去选择msi格式的&#xff0c;窗口式的安装&#xff0c;一步一步。 安装过程中有一个选项是问你需不需要配置到环境变量中&#xff0c;选上这个选项&#xff0c;不选的话&#xff0c;需要自己去配环境变量。 检查是否安装配置…

腾讯云CVM S5服务器性能如何?CPU计算性能测评

腾讯云服务器CVM标准型S5实例具有稳定的计算性能&#xff0c;CVM 2核2G S5活动优惠价格280.8元一年自带1M带宽&#xff0c;15个月313.2元、2核4G配置748.2元15个月&#xff0c;CPU内存配置还可以选择4核8G、8核16G等配置&#xff0c;公网带宽可选1M、3M、5M或10M&#xff0c;百…