01背包类问题

文章目录

  • [模版]01背包
    • 1. 第一问: 背包不一定能装满
      • (1) 状态表示
      • (2) 状态转移方程
      • (3) 初始化
      • (4) 填表顺序
      • (5) 返回值
    • 2. 第二问: 背包恰好装满
    • 3. 空间优化
  • 416.分割等和子集
    • 1. 状态表示
    • 2. 状态转移方程
    • 3. 初始化
    • 4. 填表顺序
    • 5. 返回值
  • [494. 目标和](https://leetcode.cn/problems/target-sum/description/)
    • 1. 状态表示
    • 2. 状态转移方程
    • 3. 初始化
    • 4. 填表顺序
    • 5. 返回值
  • 1049.最后一块石头的重量II
    • 1. 状态表示
    • 2. 状态转移方程
    • 3. 初始化
    • 4. 填表顺序
    • 5. 返回值

[模版]01背包

在这里插入图片描述

1. 第一问: 背包不一定能装满

(1) 状态表示

dp[i][[j]: 从前 i 个物品中挑选, 总体积不超过 j, 所有选法中, 能挑选出来的最大价值.

(2) 状态转移方程

根据最后一步的状况, 分情况讨论:

  1. 不选 i 物品
    此时就是在前 i-1 个物品中挑选, 总体积不超过 j, 所有选法中, 能挑选出来的最大价值.

dp[i][j] = dp[i-1][j]

  1. 选 i 物品
    选了 i 物品, 说明最后要加上 w[i], 此时就是在前 i-1 个物品中挑选, 总体积不超过 j - v[i], 所有选法中, 能挑选出来的最大价值.

dp[i][j] = dp[i-1][ j - v[i] ] + w[i]

注:j-v[i]可能不存在, 所以 j-v[i] >= 0
比如, j = 1, 但是 v[i] = 4 了, 此时 j-v[i] 为 负数了, 就是不存在的.

综上, 状态转移方程为:

dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[i]] + w[i])

(3) 初始化

根据题意可知, 下标是从 1 开始的, 所以 dp表 会多一行多一列.
横坐标代表物品数, 纵坐标代表体积.
第0行表示: 从前0个物品中挑选, 总体积不超过 j 的最大价值, 就是为0.
第0列表示: 从前 i 个物品中挑选, 总体积不超过 0 的最大价值, 物品都是有体积的, 这种情况也不存在, 也是为0.

所以可以不用特别的初始化, 默认为0即可.

(4) 填表顺序

从上往下

(5) 返回值

根据状态表示可知, 题目最终要返回的是, 从前 n 个物品中挑选, 总体积不超过 v 的最大价值.
即返回: dp[n][v].

2. 第二问: 背包恰好装满

与第一问的讨论思路和过程是一模一样, 状态转移方程也一样, 只有以下几点有细微的变化:

(1) 状态表示
dp[i][[j]: 从前 i 个物品中挑选, 总体积恰好等于 j, 所有选法中, 能挑选出来的最大价值.

(2) 判断状态方程是否存在
在求每一个状态时, 从前 i-1 个物品中选,可能会选不出体积恰好等于 j 的物品.
此时可以做一个约定: dp[i][j] = -1时, 表示没有这种情况.
其实在第一种情况 不选 i 物品时, 是可以不用特判 dp[i-1][j] != -1的. 因为第一种情况不选 i 物品是一定存在的, 如果 dp[i-1][j] = -1, 那么 dp[i][j] = -1, 这是合理的.
但是第二种情况 选 i 物品时一定要特判 dp[i-1][j-v[i]] != -1. 因为第二种情况多加了一个 w[i], 如果 dp[i-1][j-v[i]] 等于 -1, 再加上 w[i], 就会影响最终结果了.

(3) 初始化
第一个格子为0 , 因为正好能凑齐体积为0 的背包, 但是第一行后面的格子都是 -1 , 因为没有物品, 无法满足体积大于 0 的情况.

(4) 返回值
最后有可能凑不出体积恰好为 V 的,所以返回之前要特判一下.

代码实现如下:

#include <iostream>
#include <cstring>
using namespace std;const int N = 1010;
int n, V;
int v[N], w[N];
int dp[1010][1010];int main()
{cin >> n >> V;for(int i = 1; i <= n; i++) //下标从1开始cin >> v[i] >> w[i];//解决第一问for(int i = 1; i <= n; i++){for(int j = 1; j <= V; j++){dp[i][j] = dp[i-1][j];if(j - v[i] >= 0)dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[i]] + w[i]);}}cout << dp[n][V] << endl;//解决第二问memset(dp, 0, sizeof(dp));for(int i = 1; i <= V; i++)dp[0][i] = -1;for(int i = 1; i <= n; i++){for(int j = 1; j <= V; j++){dp[i][j] = dp[i-1][j];// 注意要特判if(j - v[i] >= 0 && dp[i-1][j-v[i]] != -1)dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[i]] + w[i]);}}cout << (dp[n][V] == -1 ? 0 : dp[n][V]) << endl;return 0;
}

3. 空间优化

1.利用滚动数组做空间上的优化
2.直接在原始的代码上修改即可
步骤:

1.把二维dp表中的 i (所有横坐标)去掉改成一维(不是改成一层循环,还是需要两层循环,只是把dp表中的 i 去掉).
2.修改 j 的遍历顺序成从右往左.

修改后的代码如下:

#include <iostream>
#include <cstring>
using namespace std;const int N = 1010;
int n, V;
int v[N], w[N];
int dp[1010];int main()
{cin >> n >> V;for(int i = 1; i <= n; i++) //下标从1开始cin >> v[i] >> w[i];//解决第一问for(int i = 1; i <= n; i++){for(int j = V; j >= 0; j--){dp[j] = dp[j];if(j - v[i] >= 0)dp[j] = max(dp[j], dp[j-v[i]] + w[i]);}}cout << dp[V] << endl;//解决第二问memset(dp, 0, sizeof(dp));for(int i = 1; i <= V; i++)dp[i] = -1;for(int i = 1; i <= n; i++){for(int j = V; j >= 0; j--){dp[j] = dp[j];// 注意要特判if(j - v[i] >= 0 && dp[j-v[i]] != -1)dp[j] = max(dp[j], dp[j-v[i]] + w[i]);}}cout << (dp[V] == -1 ? 0 : dp[V]) << endl;return 0;
}

416.分割等和子集

在这里插入图片描述

1. 状态表示

dp[i][j]:从前 i 个数中选,和是否恰好等于 j ,是为true, 不是为false.

2. 状态转移方程

和01背包问题一样, 根据第 i 个位置选或不选,分两种情况讨论:
(1) 不选第 i 个数: dp[i][j] = dp[i-1][j]
(2) 选第 i 个数: dp[i][j] = dp[i-1][j-nums[i]]
只要这两种选法中有一个能凑成就是符合题意的, 所以可得:
dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]]
注意: j - nums[i] >= 0.

3. 初始化

为了防止填表时出现越界问题,一般还是多开一行多开一列.
(0, 0) 位置, 从前 0 个数中选,和是否恰好等于 0, 成立, 所以是 true, 第一行的其他位置则为 false.
第一列, 从前 i 个数中选,和是否恰好等于 0, 成立, 所以第一列初始化为 true.

4. 填表顺序

从上往下.

5. 返回值

本题可以转化为: 在数组中选一些数, 让这些数的和等于 sum / 2.(其中sum是整个数组的和).
所以最终返回: dp[n][sum / 2] 即可.

代码实现如下:

class Solution {
public:bool canPartition(vector<int>& nums) {int n = nums.size();int sum = 0;for(auto x : nums) sum += x;//当和为奇数时,不能等和分if(sum % 2 == 1) return false;vector<vector<bool>>dp(n+1, vector<bool>(sum+1));for(int j = 1; j <= sum; j++) dp[0][j] = false;for(int i = 0; i <= n; i++) dp[i][0] = true;for(int i = 1; i <= n; i++){for(int j = 1; j <= sum; j++){dp[i][j] = dp[i-1][j];if(j - nums[i-1] >= 0)dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i-1]];}}return dp[n][sum/2];}
};

空间优化后的代码如下:

class Solution {
public:bool canPartition(vector<int>& nums) {int n = nums.size();int sum = 0;for(auto x : nums) sum += x;if(sum % 2 == 1) return false;vector<bool>dp(vector<bool>(sum+1));for(int j = 1; j <= sum; j++) dp[j] = false;for(int i = 0; i <= n; i++) dp[0] = true;for(int i = 1; i <= n; i++){for(int j = sum; j >= 1; j--){dp[j] = dp[j];if(j - nums[i-1] >= 0)dp[j] = dp[j] || dp[j-nums[i-1]];}}return dp[sum/2];}
};

494. 目标和

在这里插入图片描述
在这里插入图片描述
我们先对这道题进行分析:
在添加完±号后会有正数和负数,我们把所有正数和记为a,所有负数和的绝对值记为b,总和记为sum.
根据题意可知: a-b=target, a+b=sum,可得 a = (sum+target)/2

所以原题可转换为:在数组中选一些数,让这些数字的和等于a,一共有多少种选法.
这就是一个01背包问题, 下面的分析过程和上一题 [416.分割等和子串] 基本一样.

1. 状态表示

dp[i][j]:从前 i 个数中选,使得总和为 j ,一共有多少种选法.

2. 状态转移方程

根据i位置的状态,有两种情况:
1.i 位置不选,dp[i][j] = dp[i-1][j]
2.i 位置选,dp[i][j] = dp[i-1][j-nums[i]]
注意: j >= nums[i].
综上,两种选法的总数:
dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]]

3. 初始化

依旧是多加一行多加一列.
第一行:数组里没有元素,要凑成和为0,和为1… 都凑不出, 所以填 0, 但是 dp[0][0] = 1.
第一列:除了第一个位置,其余位置是可以不用特别初始化的.
因为本题的数字有可能是0,第一列表示的是从前 i 个数字中,总和为0的选法,那就会有很多种情况了。
而我们初始化的目的就是避免填表时越界访问,而选第 i 个位置时,是用第二种情况,这种情况我们有前提条件 j >= nums[i],当 j = 0 时, 要使用那个方程就要满足 nums[i] = 0, 此时 dp[i][j] = dp[i-1][0], 这使得这种情况填表时只会使用表中的上一个位置,而不是越界访问。
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/04948e2307da45a3a6d4ba01722ca7b6.png

4. 填表顺序

从上往下

5. 返回值

根据我们上面的分析可知, 最终返回: dp[n][a] 即可.

代码实现如下:

class Solution {
public:int findTargetSumWays(vector<int>& nums, int target) {int n = nums.size();int sum = 0;for(auto x : nums) sum += x;int a = (sum + target) / 2;if(a < 0 || (sum + target) % 2 == 1) return 0;vector<vector<int>> dp(n+1, vector<int>(a+1));dp[0][0] = 1;for(int i = 1; i <= n; i++){for(int j = 0; j <= a; j++){dp[i][j] = dp[i-1][j];if(j >= nums[i-1])dp[i][j] += dp[i-1][j-nums[i-1]];}}return dp[n][a];}
};

空间优化后的代码如下:

class Solution {
public:int findTargetSumWays(vector<int>& nums, int target) {int n = nums.size();int sum = 0;for(auto x : nums) sum += x;int a = (sum + target) / 2;if(a < 0 || (sum + target) % 2 == 1) return 0;vector<int> dp(vector<int>(a+1));dp[0] = 1;for(int i = 1; i <= n; i++){for(int j = a; j >= 1; j--){dp[j] = dp[j];if(j >= nums[i-1])dp[j] += dp[j-nums[i-1]];}}return dp[a];}
};

1049.最后一块石头的重量II

在这里插入图片描述
我们先来分析题目:
挑选两个石头粉碎,其实就是在任意两个石头前添加±号
分析思路同"目标和"一题,全部正数和记为a,负数和绝对值记为b. 要求的就是|a-b|的最小值。
而我们又知道全部数的总和sum, 即a+b=sum.
求|a-b|的最小值可以说成: 把一个数sum拆成两个数,求这两个数的差的最小值.
而只有当这两个数越接近sum/2时,差就越小
综上分析,本题可转换为:
在数组中选择一些数,让这些数的和尽可能接近sum/2("这些数的和"就是上面的a或b).

本质就是一个01背包问题:
物品 - 数
每个物品的价值 - nums[i]
每个物品体积 - nums[i]
背包体积 - sum/2.
选一些数放进背包中,在不超过背包体积的情况下,里面的最大和是多少.

1. 状态表示

dp[i][j]:从前i个数中选,总和不超过j,此时的最大和.

2. 状态转移方程

根据i位置的状态,有两种情况:
1.i 位置不选,dp[i][j] = dp[i-1][j]
2.i 位置选,dp[i][j] = dp[i-1][j-nums[i]] + nums[i]
注意: j >= nums[i].
综上: dp[i][j] = max(dp[i-1][j] , dp[i-1][j-nums[i]])

3. 初始化

多一行多一列
第一行:背包中没有石头,无法凑成总和为0,1,2,3… 初始化为0即可
第一列:同 [目标和] 一题.

4. 填表顺序

从上往下

5. 返回值

dp[n][sum/2] 就是上面分析中的 a,则 b = sum-dp[n][sum/2], 所以最小值为 a - b = sum - 2 * dp[n][sum/2].

代码实现如下:

class Solution 
{
public:int lastStoneWeightII(vector<int>& stones) {int n = stones.size(), sum = 0;for(auto x : stones) sum += x;vector<vector<int>> dp(n+1, vector<int>(sum / 2 + 1));for(int i = 1; i <= n; i++){for(int j = 0; j <= sum / 2; j++){dp[i][j] = dp[i-1][j];if(j >= stones[i-1])dp[i][j] = max(dp[i-1][j], dp[i-1][j-stones[i-1]] + stones[i-1]);}}// dp[n][sum/2]就是上面分析中的a,则b=sum-dp[n][sum/2]// 所以最小值为a-b=sum - 2 * dp[n][sum/2]return sum - 2 * dp[n][sum/2];}
};

空间优化后的代码:

class Solution 
{
public:int lastStoneWeightII(vector<int>& stones) {int n = stones.size(), sum = 0;for(auto x : stones) sum += x;vector<int> dp(vector<int>(sum / 2 + 1));for(int i = 1; i <= n; i++){for(int j = sum / 2; j >= 1; j--){dp[j] = dp[j];if(j >= stones[i-1])dp[j] = max(dp[j], dp[j-stones[i-1]] + stones[i-1]);}}// dp[n][sum/2]就是上面分析中的a,则b=sum-dp[n][sum/2]// 所以最小值为a - b = sum - 2 * dp[sum/2]return sum - 2 * dp[sum/2];}
};

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

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

相关文章

解锁 DevOps 新境界 :使用 Flux 进行 GitOps 现场演示 – 自动化您的 Kubernetes 部署

前言 GitOps 是实现持续部署的云原生方式。它的名字来源于标准且占主导地位的版本控制系统 Git。GitOps 的 Git 在某种程度上类似于 Kubernetes 的 etcd&#xff0c;但更进一步&#xff0c;因为 etcd 本身不保存版本历史记录。毋庸置疑&#xff0c;任何源代码管理服务&#xf…

将Docker镜像变为可执行文件?体验docker2exe带来的便捷!

在现代软件开发中,容器化技术极大地改变了应用程序部署和管理的方式。Docker,作为领先的容器化平台,已经成为开发者不可或缺的工具。然而,对于不熟悉Docker的用户来说,接触和运行Docker镜像可能会是一个复杂的过程。为了解决这一问题,docker2exe项目应运而生。它提供了一…

IBM BAW(原BPM升级版)使用教程第八讲

续前篇&#xff01; 一、流程开发功能模块使用逻辑和顺序 前面我们已经对 流程、用户界面、公开的自动化服务、服务、事件、团队、数据、性能、文件各个模块进行了详细讲解&#xff0c;现在统一进行全面统一讲解。 在 IBM Business Automation Workflow (BAW) 中&#xff0c;…

针对共享内存和上述windows消息机制 在C++ 和qt之间的案例 进行详细举例说明

针对共享内存和上述windows消息机制 在C++ 和qt之间的案例 进行详细举例说明 以下是关于在 C++ 和 Qt 中使用共享内存(QSharedMemory)和 Windows 消息机制(SendMessage / PostMessage)进行跨线程或跨进程通信的详细示例。 🧩 使用 QSharedMemory 进行进程间通信(Qt 示例…

jetson orin nano super AI模型部署之路(十)使用frp配置内网穿透,随时随地ssh到机器

为什么要内网穿透&#xff1f; 我们使用jetson设备时&#xff0c;一般都是在局域网内的电脑去ssh局域网内的jetson设备&#xff0c;但是这种ssh或者VNC仅限于局域网之间的设备。 如果你出差了&#xff0c;或者不在jetson设备的局域网内&#xff0c;想再去ssh或者VNC我们的jet…

VScode密钥(公钥,私钥)实现免密登录【很细,很全,附带一些没免密登录成功的一些解决方法】

一、 生成SSH密钥对 ssh-keygen 或者 ssh-keygen -t rsa -b 4096区别&#xff1a;-t rsa可以明确表示生成的是 RSA 类型的密钥-b参数将密钥长度设置为 4096 位默认&#xff1a;2048 位密钥不指定-t参数&#xff0c;ssh -keygen默认也可能生成 RSA 密钥【确保本机安装ssh&#…

解释器和基于规则的系统比较

解释器&#xff08;Interpreter&#xff09;和基于规则的系统&#xff08;Rule-Based System&#xff09;是两种不同的软件架构风格&#xff0c;分别适用于不同的应用场景。它们在设计理念、执行机制和适用领域上有显著差异。以下是它们的核心对比&#xff1a; 1. 解释器&#…

DB4S:一个开源跨平台的SQLite数据库管理工具

DB Browser for SQLite&#xff08;DB4S&#xff09;是一款开源、跨平台的 SQLite 数据库管理工具&#xff0c;用于创建、浏览和编辑 SQLite 以及 SQLCipher 数据库文件。 功能特性 DB4S 提供了一个电子表格风格的数据库管理界面&#xff0c;以及一个 SQL 查询工具。DB4S 支持…

printf调试时候正常,运行时打印不出来

问题是在添加了 printf 功能后&#xff0c;程序独立运行时无法正常打印输出&#xff0c;而调试模式下正常。这表明问题可能与 printf 的重定向实现、标准库配置、或编译器相关设置有关。 解决&#xff1a; 原来是使用 Keil/IAR&#xff0c;printf可能需要启用 MicroLIB 或正确…

轻松制作高质量视频,实时生成神器LTX-Video重磅登场!

探索LTX-Video&#xff1a;实时视频生成跨越新高度 在如今这个视觉内容主导的数字时代&#xff0c;视频生成成为推动创意表达的关键。而今天&#xff0c;我们将带您深入探索LTX-Video&#xff0c;一个强大的开源项目&#xff0c;致力于通过尖端技术将视频生成提升到一个全新的…

分布式事务快速入门

分布式事务基本概念 使用分布式事务的场景&#xff1a;分布式场景下的跨数据库事务 分布式事务诞生的理论&#xff1a;CAP和Base 3种一致性&#xff1a; 强一致性 &#xff1a;系统写入了什么&#xff0c;读出来的就是什么。 弱一致性 &#xff1a;不一定可以读取到最新写入…

nvme Unable to change power state from D3cold to D0, device inaccessible

有个thinkpad l15 gen4笔记本&#xff0c;使用较少&#xff0c;有一块三星m2和东芝14t硬盘&#xff0c;想安装飞牛nas系统作为家庭照片库&#xff0c;制作飞牛启动盘&#xff0c;发现安装飞牛需要全盘格式化&#xff0c;电脑本身的系统还是需要保留的&#xff0c;故想到再安装一…

Unity Shaders and Effets Cookbook

目录 作者简介 审稿人简介 前言 我是偏偏 Unity Shaders and Effets Cookbook 第一章&#xff1a;Diffuse Shading - 漫反射着色器 第二章&#xff1a;Using Textures for Effects - 着色器纹理特效的应用 第三章&#xff1a;Making Your Game Shine with Specular - 镜…

部署RocketMQ

部署环境&#xff1a;jdk8以上&#xff0c;Linux系统 下载和安装指令&#xff1a; wget https://archive.apache.org/dist/rocketmq/4.9.4/rocketmq-all-4.9.4-bin-release.zip 显示下载成功&#xff1a; --2025-05-10 11:34:46-- https://archive.apache.org/dist/rocketm…

使用FastAPI和React以及MongoDB构建全栈Web应用04 MongoDB快速入门

一、NoSQL 概述 1.1 了解关系数据库的局限性 Before diving into NoSQL, it’s essential to understand the challenges posed by traditional Relational Database Management Systems (RDBMS). While RDBMS have been the cornerstone of data management for decades, th…

高精度之加减乘除之多解总结(加与减篇)

开篇总述&#xff1a;精度计算的教学比较杂乱&#xff0c;无系统的学习&#xff0c;且存在同法多线的方式进行同一种运算&#xff0c;所以我写此篇的目的只是为了直指本质&#xff0c;不走教科书方式&#xff0c;步骤冗杂。 一&#xff0c;加法 我在此讲两种方法&#xff1a; …

气象大模型光伏功率预测中的应用:从短期,超短期,中长期的实现与开源代码详解

1. 引言 光伏功率预测对于电力系统调度、能源管理和电网稳定性至关重要。随着深度学习技术的发展,大模型(如Transformer、LSTM等)在时间序列预测领域展现出强大能力。本文将详细介绍基于大模型的光伏功率预测方法,涵盖短期(1-6小时)、超短期(15分钟-1小时)和中长期(1天-1周…

玩转Docker(一):基本概念

容器技术是继大数据和云计算之后又一炙手可热的技术&#xff0c;而且未来相当一段时间内都会非常流行。 本文将对其基本概念和基本使用做出介绍。包括容器生态系统、容器的原理、怎样运行第一个容器、容器技术的概念与实践、Docker镜像等等 目录 一. 鸟瞰容器生态系统 1. 容器…

计算机视觉与深度学习 | 基于数字图像处理的裂缝检测与识别系统(matlab代码)

🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅 基于数字图像处理的裂缝检测与识别系统 🥦🥦🥦🥦🥦🥦🥦🥦🥦🥦🥦🥦🥦**系统架构设计****1. 图像预处理**目标:消除噪声+增强裂缝特征**2. 图像分割**目标:提取裂缝区域**3. 特征…

推荐一款免费开源工程项目管理系统软件,根据工程项目全过程管理流程开发的OA 办公系统

在当今的工程项目管理领域&#xff0c;许多企业和团队面临着诸多难题。传统的管理方式往往依赖于人工记录和分散的工具&#xff0c;导致项目进度难以实时把控&#xff0c;任务分配不够清晰&#xff0c;合同管理混乱&#xff0c;事件提醒不及时&#xff0c;财务管理缺乏系统性&a…