代码随想录算法训练营day42 |动态规划之背包问题 11.分割等和子集 1049. 最后一块石头的重量 II 494. 目标和 474.一和零

news2024/9/30 23:39:09

day42

      • 11.分割等和子集
        • 1.dp数组的含义
        • 2.确定递推公式
        • 3.dp数组初始化
        • 4.确定遍历顺序
        • 5.打印dp数组
      • 1049. 最后一块石头的重量 II
        • 1.确定dp数组以及下标的含义
        • 2.确定递推公式
        • 3.dp数组如何初始化
        • 4.确定遍历顺序
        • 5.举例推导dp数组
      • 494. 目标和
        • 1.确定dp数组以及下标的含义
        • 2.确定递推公式
        • 3.dp数组如何初始化
        • 4.确定遍历顺序
        • 5.举例推导dp数组

11.分割等和子集

题目链接
解题思路: 将数组每个元素相加就和,然后除以2,假设相除以后的结果是K,即看数组里的元素能否将K值填满,每个元素只能用一次。看时候抽象理解成为一个01背包问题,然后使用动归五部曲来解决这个问题。

1.dp数组的含义

套到本题,dp[j]表示 背包总容量(所能装的总重量)是j,放进物品后,背的最大重量为dp[j]
注:本题中每一个元素的数值既是重量,也是价值。

2.确定递推公式

dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);

3.dp数组初始化

本题题目中 只包含正整数的非空数组,所以非0下标的元素初始化为0就可以了。
代码如下:

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

4.确定遍历顺序

使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历!
代码如下:

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

5.打印dp数组

dp[j]的数值一定是小于等于j的。

如果dp[j] == j 说明,集合中的子集总和正好可以凑成总和j,理解这一点很重要。

用例1,输入[1,5,11,5] 为例,如图:
在这里插入图片描述

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;
    }
};

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

题目链接
解题思路: 尽量把中石头分成两组,两组的重量总和比较接近。整体思路和上一题基本上一致,只是最后判断的过程有一些区别。
动规五步曲:

1.确定dp数组以及下标的含义

dp[j]表示容量(这里说容量更形象,其实就是重量)为j的背包,最多可以背最大重量为dp[j]

相对于 01背包,本题中,石头的重量是 stones[i],石头的价值也是 stones[i] ,可以 “最多可以装的价值为 dp[j]” == “最多可以背的重量为dp[j]

2.确定递推公式

本题则是:dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);

3.dp数组如何初始化

既然 dp[j]中的j表示容量,那么最大容量(重量)是多少呢,就是所有石头的重量和。

因为提示中给出1 <= stones.length <= 30,1 <= stones[i] <= 100,所以最大重量就是30 * 100 。
而我们要求的 target 其实只是最大重量的一半,所以dp数组开到1500大小就可以了。
代码为:

vector<int> dp(15001, 0);

4.确定遍历顺序

如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历!
代码如下:

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

5.举例推导dp数组

输入:[2,4,1,1],此时target = (2 + 4 + 1 + 1)/2 = 4 ,dp数组状态图如下:
在这里插入图片描述
dp[target]里是容量为target的背包所能背的最大重量。

那么分成两堆石头,一堆石头的总重量是dp[target],另一堆就是sum - dp[target]

在计算target的时候,target = sum / 2 因为是向下取整,所以sum - dp[target] 一定是大于等于dp[target]的。

那么相撞之后剩下的最小石头重量就是 (sum - dp[target]) - dp[target]

class Solution {
public:
    int lastStoneWeightII(vector<int>& stones) {
        vector<int> dp(15001, 0);
        int sum = 0;
        for (int i = 0; i < stones.size(); i++) sum += stones[i];
        int target = sum / 2;
        for (int i = 0; i < stones.size(); i++) { // 遍历物品
            for (int j = target; j >= stones[i]; j--) { // 遍历背包
                dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);
            }
        }
        return sum - dp[target] - dp[target];
    }
};

494. 目标和

题目链接
解题思路:
假设加法的总和为x,那么减法对应的总和就是sum - x
所以我们要求的是 x - (sum - x) = target

x = (target + sum) / 2

此时问题就转化为,装满容量为x的背包,有几种方法。

这里的x,就是bagSize,也就是我们后面要求的背包容量。

大家看到(target + sum) / 2 应该担心计算的过程中向下取整有没有影响。

这么担心就对了,例如sum 是5,S是2的话其实就是无解的,所以:

(C++代码中,输入的S 就是题目描述的 target)
if ((S + sum) % 2 == 1) return 0; // 此时没有方案

同时如果 S的绝对值已经大于sum,那么也是没有方案的。

(C++代码中,输入的S 就是题目描述的 target)
if (abs(S) > sum) return 0; // 此时没有方案

再回归到01背包问题,为什么是01背包呢?

因为每个物品(题目中的1)只用一次!

这次和之前遇到的背包问题不一样了,之前都是求容量为j的背包,最多能装多少。

本题则是装满有几种方法。其实这就是一个组合问题了。

1.确定dp数组以及下标的含义

dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法

2.确定递推公式

有哪些来源可以推出dp[j]呢?

只要搞到nums[i]),凑成dp[j]就有dp[j - nums[i]] 种方法。

例如:dp[j],j 为5,

  • 已经有一个1(nums[i]) 的话,有 dp[4]种方法 凑成 容量为5的背包。
  • 已经有一个2(nums[i]) 的话,有 dp[3]种方法 凑成 容量为5的背包。
  • 已经有一个3(nums[i]) 的话,有 dp[2]中方法 凑成 容量为5的背包
  • 已经有一个4(nums[i]) 的话,有 dp[1]中方法 凑成 容量为5的背包
  • 已经有一个5 (nums[i])的话,有 dp[0]中方法 凑成 容量为5的背包

那么凑整dp[5]有多少方法呢,也就是把 所有的 dp[j - nums[i]] 累加起来。

所以求组合类问题的公式,都是类似这种:

dp[j] += dp[j - nums[i]]

这个公式在后面在讲解背包解决排列组合问题的时候还会用到!

3.dp数组如何初始化

从递推公式可以看出,在初始化的时候dp[0] 一定要初始化为1,因为dp[0]是在公式中一切递推结果的起源,如果dp[0]是0的话,递推结果将都是0。

这里有录友可能认为从dp数组定义来说 dp[0] 应该是0,也有录友认为dp[0]应该是1。

其实不要硬去解释它的含义,咱就把 dp[0]的情况带入本题看看应该等于多少。

如果数组[0] ,target = 0,那么 bagSize = (target + sum) / 2 = 0。 dp[0]也应该是1, 也就是说给数组里的元素 0 前面无论放加法还是减法,都是 1 种方法。

所以本题我们应该初始化 dp[0] 为 1。

可能有同学想了,那 如果是 数组[0,0,0,0,0] target = 0 呢。

其实 此时最终的dp[0] = 32,也就是这五个零 子集的所有组合情况,但此dp[0]非彼dp[0],dp[0]能算出32,其基础是因为dp[0] = 1 累加起来的。

dp[j]其他下标对应的数值也应该初始化为0,从递推公式也可以看出,dp[j]要保证是0的初始值,才能正确的由dp[j - nums[i]]推导出来。

4.确定遍历顺序

和01背包问题一维dp的遍历一样,nums放在外循环,target在内循环,且内循环倒序。

5.举例推导dp数组

输入:nums: [1, 1, 1, 1, 1], S: 3
bagSize = (S + sum) / 2 = (3 + 5) / 2 = 4

dp数组状态变化如下:
在这里插入图片描述

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int S) {
        int sum = 0;
        for (int i = 0; i < nums.size(); i++) sum += nums[i];
        if (abs(S) > sum) return 0; // 此时没有方案
        if ((S + sum) % 2 == 1) return 0; // 此时没有方案
        int bagSize = (S + sum) / 2;
        vector<int> dp(bagSize + 1, 0);
        dp[0] = 1;
        for (int i = 0; i < nums.size(); i++) {
            for (int j = bagSize; j >= nums[i]; j--) {
                dp[j] += dp[j - nums[i]];
            }
        }
        return dp[bagSize];
    }
};

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

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

相关文章

ChatGPT懂SAP吗?跟ChatGPT聊完后,我暂时不担心会失业

我弄了个ChatGPT账号。随便问了一下他SAP的问题。贴出来跟大家分享一下。 问题1&#xff1a;我是 SAP 开发工程师&#xff0c;你有什么可以帮到我。 作为一个语言模型&#xff0c;我可以为您提供关于SAP开发的一般性建议&#xff0c;以及关于如何使用SAP软件的一些技巧。以下是…

【华为OD机试模拟题】用 C++ 实现 - 合规数组(2023.Q1)

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

流感来了,这类人最容易感染!

最近有学校因多名学生发热停课&#xff0c;浙江多地疾控也提醒大家现在是进入了甲流高发期。今天就来讲一讲甲流该如何防护。首先甲流与普通感冒不同&#xff0c;感冒病原体是鼻病毒、冠状病毒、副流感病毒等。流感病毒是正粘病毒科&#xff0c;根据核蛋白和基质蛋白M1抗原性的…

六千字让你明白什么是数字孪生?

文章目录1. 背景2. 数字孪生基础2.1 概念2.2 价值3. 技术生态3.1 技术体系3.2 核心技术3.2.1 多领域、多尺度融合建模3.2.2 数据驱动与物理模型融合的状态评估3.2.3 数据采集和传输3.2.4 全生命周期数据管理3.2.5 虚拟现实呈现3.2.6 高性能计算3.3 建设3.3.1 重点3.3.1.1 数字孪…

3款强大且实用的电脑软件,颠覆你的认知,值得一试

闲话少说&#xff0c;直上狠货。 1、一个木函 一个木函仅一张照片的体积&#xff0c;却提供了与日常、图片、设备、文件、文字处理等等相关的80多种工具&#xff0c;相当实用&#xff0c;更牛的是&#xff0c;完全免费&#xff0c;无任何弹屏广告。一个木函体积小&#xff0c;简…

力扣-游戏玩法分析

大家好&#xff0c;我是空空star&#xff0c;本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目&#xff1a;511. 游戏玩法分析二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交SQL运行结果5.其他总结…

【Acwing 周赛复盘】第91场周赛复盘(2023.2.18)

【Acwing 周赛复盘】第91场周赛复盘&#xff08;2023.2.18&#xff09; 周赛复盘 ✍️ 本周个人排名&#xff1a;1286/3115 AC情况&#xff1a;2/3 这是博主参加的第六次周赛&#xff0c;周赛当晚有事&#xff0c;是后来定时自测的 &#x1f602; 在 20 分钟内 AC 了 2 题&…

计算机408考研先导课---C语言难点2

目录 一、字符型数据与字符串型数据的比较 1、字符型数据特点 2、字符串型数据特点 二、字符数组 1、定义 2、输入输出 ①输入 ②输出 3、字符处理函数 ①put函数 ②gets函数 ③strcat函数 ④strcpy函数 ⑤strcmp函数 ⑥strlen函数 ⑦strlwr函数 ⑧strup…

LeetCode练习三:链表

文章目录一、链表基础1.1 无序表&#xff08;UnorderedList&#xff09;1.1.2 双向链表1.1.3 循环链表1.2 链表的基本操作1.2.1 定义链表结构1.2.2 建立线性链表1.2.3 求线性链表的长度1.2.4 查找元素1.2.5 插入元素1.2.6 改变元素1.2.7 删除元素1.3 有序表OrderedList1.4 链表…

Redis之消息队列实现

文章目录秒杀场景采用消息队列实现List实现消息队列PubSub&#xff08;发布订阅&#xff09;实现消息队列基于Stream实现消息队列消费者组实践总结秒杀问题是非常重要且比较难实现的&#xff0c;如果不进行架构的优化的话&#xff0c;直接访问会给业务系统造成很大的压力… 秒杀…

【Linux】system V共享内存 | 消息队列 | 信号量

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《学会Linux》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;system V共…

go进阶(2) -深入理解Channel实现原理

Go的并发模型已经在https://guisu.blog.csdn.net/article/details/129107148 详细说明。 1、channel使用详解 1、channel概述 Go的CSP并发模型&#xff0c;是通过goroutine和channel来实现的。 channel是Go语言中各个并发结构体(goroutine)之前的通信机制。 通俗的讲&#xf…

通用信息抽取技术UIE产业案例解析,Prompt 范式落地经验分享!

想了解用户的评价究竟是“真心夸赞”还是“阴阳怪气”&#xff1f;想快速从多角色多事件的繁杂信息中剥茧抽丝提取核心内容&#xff1f;想通过聚合相似事件准确地归纳出特征标签&#xff1f;……想了解UIE技术在产业中的实战落地经验&#xff1f;通用信息抽取技术 UIE 产业案例…

FPGA基础知识

FPGA是在PAL、PLA和CPLD等可编程器件的基础上进一步发展起来的一种更复杂的可编程逻辑器件。它是ASIC领域中的一种半定制电路&#xff0c;既解决了定制电路的不足&#xff0c;又克服了原有可编程器件门电路有限的缺点。 由于FPGA需要被反复烧写&#xff0c;它实现组合逻辑的基…

【强化学习】强化学习数学基础:贝尔曼公式

强化学习数学基础&#xff1a;贝尔曼公式强化学习的数学原理课程总览贝尔曼公式&#xff08;Bellman Equation&#xff09;一个示例状态值贝尔曼公式&#xff1a;推导过程贝尔曼公式&#xff1a;矩阵-向量形式&#xff08;Matrix-vector form&#xff09;贝尔曼公式&#xff1a…

(四)应变度量

本文主要内容包括&#xff1a;1. Hill 应变度量 与 Seth 应变度量2. Hill -Seth 应变度量的 Lagrange 描述2.1. Green-Lagrange 应变张量2.2. 物质 Biot 应变张量/工程应变2.3. 右 Henkey 应变张量/Lagrange 型对数应变2.4. Piola 应变张量3. Hill -Seth 应变度量的 Euler 描述…

Tesla都使用什么编程语言?

作者 | 初光 出品 | 车端 备注 | 转载请阅读文中版权声明 知圈 | 进“汽车电子与AutoSAR开发”群&#xff0c;请加微“cloud2sunshine” 总目录链接>> AutoSAR入门和实战系列总目录 带着对更美好未来的愿景&#xff0c;特斯拉不仅成为有史以来最有价值的汽车公司&…

乐友商城学习笔记(五)

什么是Nginx Nginx是一个高性能的web和反向代理服务器 作为web服务器作为负载均衡服务器作为邮件代理服务 树组件的用法 跨域问题 跨域&#xff1a;浏览器对javastript的同源策略的限制。 以下情况都属于跨域&#xff1a; 域名不同域名相同&#xff0c;端口不同二级域名不…

Python每日一练(20230225)

目录 1. 整数反转 2. 求最大公约数和最小公倍数 最大公约数 最小公倍数 3. 单词搜索 II 附录&#xff1a; DFS 深度优先搜索算法 BFS 广度优先搜索算法 BFS 和 DFS 的区别 1. 整数反转 给你一个 32 位的有符号整数 x &#xff0c;返回将 x 中的数字部分反转后的结果。…

大型旋转设备滑动轴承X、Y测点振动值说明(转载的)

滑动轴承支撑的大型旋转设备&#xff0c;绝大部分的故障都表现为不平衡引起的1倍频振动&#xff0c;诊断故障原因要根据振动随转速、负荷、温度、时间的变化情况来具体判断。滑动轴承设备的诊断主要依据电涡流传感器测量轴和轴瓦间的相对振动&#xff0c;判断转子相关的各种问题…