第四十三天| 1049. 最后一块石头的重量 II、494. 目标和、474.一和零

01背包问题

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

题目链接:1049 最后一块石头的重量 II

题干:有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。

每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

  • 如果 x == y,那么两块石头都会被完全粉碎;
  • 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x

最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0

思考:动态规划。难点在于:想到尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小。从而可以看出是01背包问题的类似题。


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

dp[j]表示容量(或可承受重量)为j的背包,最多能装下的价值(石头总重量)。

  • 确定递推公式

01背包的递推公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

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

  • dp数组如何初始化

由于想将石头分成两堆,因此数组的大小只要能装下总重量的一半即可。

此外由于重量不可能为负数,dp[i]都初始为0即可。

  • 确定遍历顺序

由于使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历。

  • 举例推导dp数组

举例,输入:[2,4,1,1],dp数组状态图如下:

最后dp[sum / 2]里是容量为sum / 2的背包所能背的最大重量。 那么分成两堆石头,一堆石头的总重量是dp[sum / 2],另一堆就是sum - dp[sum / 2]。

sum / 2 因为是向下取整,所以sum - dp[sum / 2] 一定是大于等于dp[sum / 2]的。那么相撞之后剩下的最小石头重量就是 (sum - dp[sum / 2]) - dp[sum / 2]。

代码:

class Solution {
public:int lastStoneWeightII(vector<int>& stones) {int sum = 0;for (int n : stones)     //统计石头总重量sum += n;vector<int> dp(sum / 2 + 1, 0);     //dp[i]表示容量为i的背包装下的石头总重量for (int i = 0; i < stones.size(); i++)         //遍历石头for (int j = sum / 2; j >= stones[i]; j--)  //遍历背包容量,只考虑放得下的情况dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);      //递推公式return sum - dp[sum / 2] - dp[sum / 2];}
};

Leetcode 494. 目标和

题目链接:494 目标和

题干:给你一个非负整数数组 nums 和一个整数 target 。

向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 :

  • 例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" 。

返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

难点:此题的难点在于如何使表达式结果为target。

若从往数字前面添加正负号的角度去思考,此题较难处理。

若将问题看成划分数组成两堆,一堆加正号(记作左堆left),另一堆加负号(记作右堆right),两堆相加(left - right)结果为target,则解题目标从计算添加正负号形成不同表达式的数目转换为计算数组累加结果为left的组合个数。至于left大小:
首先left - right = target;

其次left + right = sum(数组内所有数字相加之和),从而left = (target + sum) / 2.

当然此题需要剪枝,若sum < target则所有数字相加都达不到目标值即不存在想要的表达式。

其次 由于left是累加之和为整数,则若target + sum 为奇数则也不存在想要的表达式。

思考一:回溯法。此题在理清难点后,和39 组合总和类似。终止条件为累计值为目标值left。由于每种组合数组中的数字只能取一次,因此参数要加上startIndex。

代码:

class Solution {
public:int result = 0;void backtracking(const vector<int>& nums, const int target, int sum, int startIndex) {if (sum == target) {        //找到匹配目标值result++; return;}       for (int i = startIndex; i < nums.size(); i++)      //从startIndex开始遍历,不重复取数if (sum + nums[i] <= target)backtracking(nums, target, sum + nums[i], i + 1);}int findTargetSumWays(vector<int>& nums, int target) {int sum = 0;for (int n : nums)sum += n;if (sum < target || - sum > target)   return 0;       //总和小于目标值绝对值则不存在表达式if ((sum + target) % 2 == 1)    return 0;       //作为正数的累计值必须为整数int left = (sum + target) / 2;      //所需正数的累加之和backtracking(nums, left, 0, 0);return result;}
};

思考二:动态规划。由于每种组合数组中的数字只能取一次,因此考虑01背包。

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

dp[j] 表示:从数组中任选数字,累加和为i的选取方式的数目

  • 确定递推公式

从dp[ i ]的定义,可以看出有多个方向可以推出dp[ i ]的部分。

当处理的数字为nums[ j ]时,dp[ i ] 的部分可以从累加为 i - nums[ j ]的情况加上nums[ j ]得到。

因此只要搞到nums[ j ],凑成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]]。

  • dp数组如何初始化

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

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

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

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

  • 确定遍历顺序

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

  • 举例推导dp数组

举例:nums: [1, 1, 1, 1, 1], target: 3。此时bagSize = (target + sum) / 2 = (3 + 5) / 2 = 4。

dp数组状态变化如下:

代码:

class Solution {
public:int findTargetSumWays(vector<int>& nums, int target) {int sum = 0;for (int n : nums)sum += n;if (sum < target || - sum > target)   return 0;       //总和小于目标值绝对值则不存在表达式if ((sum + target) % 2 == 1)    return 0;       //作为正数的累计值必须为整数int left = (sum + target) / 2;      //所需正数的累加之和vector<int> dp(left + 1, 0);        //dp[i]表示从数组中任选数字,累加和为i的选取方式的数目dp[0] = 1;      //初始化for (int i = 0; i < nums.size(); i++)           //遍历数字for (int j = left; j >= nums[i]; j--)        //遍历累加和的形成方式数目dp[j] += dp[j - nums[i]];       //递推公式 累加各种累加途径的数目return dp[left];}
};

Leetcode 474.一和零

题目链接:474 一和零

题干:给你一个二进制字符串数组 strs 和两个整数 m 和 n 。

请你找出并返回 strs 的最大子集的长度,该子集中 最多 有 m 个 0 和 n 个 1 。

如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。

思考:动态规划。本题中strs 数组里的元素就是物品,每个物品都是一个!而m 和 n相当于是一个背包,两个维度的背包。因此本题仍为01背包问题的类似题。


  • 确定dp数组(dp table)以及下标的含义

dp[i][j]:规定集合中0的个数为i,1的个数为j下,集合中最大元素个数

  • 确定递推公式

dp[i][j] 可以由前一个strs里的字符串推导出来,strs里的字符串有zeroNum个0,oneNum个1。

dp[i][j] 就可以是 dp[i - zeroNum][j - oneNum] + 1。遍历的过程中取dp[i][j]的最大值。

因此递推公式:dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);

字符串的zeroNum和oneNum相当于01背包问题中的物品重量(weight[i]),字符串本身的个数相当于物品的价值(value[i])。

  • dp数组如何初始化

因为物品价值不会是负数,所以初始为0,保证递推的时候dp[i][j]不会被初始值覆盖。

  • 确定遍历顺序

01背包是外层for循环遍历物品,内层for循环遍历背包容量且从后向前遍历!

那么本题也是,物品就是strs里的字符串,背包容量就是题目描述中的m和n。遍历背包容量的两层for循环先后循序没什么讲究,都只是物品重量的一个维度。

  • 举例推导dp数组

举例:["10","0001","111001","1","0"],m = 3,n = 3。最后dp数组的状态如下所示:

代码:

class Solution {
public:int findMaxForm(vector<string>& strs, int m, int n) {  vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));     //dp[i][j]表示规定集合中0的个数为i,1的个数为j下,集合中最大元素个数for (string str : strs) {      //遍历字符串int zeroNum = 0;for (int i = 0; i < str.size(); i++)        //统计字符串中0的个数if (str[i] == '0')zeroNum++;int oneNum = str.size() - zeroNum;      //记录字符串中1的个数for (int i = m; i >= zeroNum; i--)         //遍历规定集合中0的个数for (int j = n; j >= oneNum; j--)     //遍历规定集合中1的个数dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);      //递推公式}                return dp[m][n];}
};

自我总结:

清晰01背包问题的判断理由:物品不可重复取。确定背包容量,物体重量以及物品价值,考虑dp数组元素的来源来确定递推公式,最后确定数组初始化情况以及dp数组遍历顺序,便可举例验证。

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

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

相关文章

网域图片的访问下载路径

网域图片的本身内容资源在网络空间中的访问下载路径

Unity(第十七部)Unity自带的角色控制器

组件Character Controller 中文角色控制器 using System.Collections; using System.Collections.Generic; using UnityEngine;public class player : MonoBehaviour {private CharacterController player;void Start(){player GetComponent<CharacterController>();}v…

Linux基本指令(上)

在Linux中&#xff0c;将文件夹称为目录&#xff0c;后面的内容都与目录相关。 1. ls指令 语法&#xff1a; ls [选项][目录或文件] 功能&#xff1a;对于目录&#xff0c;该命令列出该目录下的所有子目录与文件。对于文件&#xff0c;将列出文件名以及其他信息。 常用选项 …

Java ElasticSearch-Linux面试题

Java ElasticSearch-Linux面试题 前言1、守护线程的作用&#xff1f;2、链路追踪Skywalking用过吗&#xff1f;3、你对G1收集器了解吗&#xff1f;4、你们项目用的什么垃圾收集器&#xff1f;5、内存溢出和内存泄露的区别&#xff1f;6、什么是Spring Cloud Bus&#xff1f;7、…

Springboot解决模块化架构搭建打包错误找不到父工程

Springboot解决模块化架构搭建打包错误找不到父工程 一、情况一找不到父工程依赖1、解决办法 二、情况二子工程相互依赖提示"程序包xxx不存在" 一、情况一找不到父工程依赖 报错信息 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-deploy-plugin:…

使用Node.js构建一个简单的聊天机器人

当谈到人工智能&#xff0c;我们往往会想到什么&#xff1f;是智能语音助手、自动回复机器人等。在前端开发领域中&#xff0c;我们也可以利用Node.js来构建一个简单而有趣的聊天机器人。本文将带你一步步实现一个基于Node.js的聊天机器人&#xff0c;并了解其工作原理。 首先…

安装 Ubuntu 22.04.3 和 docker

文章目录 一、安装 Ubuntu 22.04.31. 简介2. 下载地址3. 系统安装4. 系统配置 二、安装 Docker1. 安装 docker2. 安装 docker compose3. 配置 docker 一、安装 Ubuntu 22.04.3 1. 简介 Ubuntu 22.04.3 是Linux操作系统的一个版本。LTS 版本支持周期到2032年。 系统要求双核 C…

linux c++ 开发 tensorrt 安装

tensorrt 官方下载地址&#xff08;需要注册账号登录&#xff09;&#xff1a;Log in | NVIDIA Developer 根据系统发行版和CUDA版本 (nvcc -V) 选择合适的安装包 EA&#xff08;early access&#xff09;版本代表抢先体验。 GA&#xff08;general availability&#xff09;代…

创新永不止步,织信低代码平台继续加速前进!

2023年&#xff0c;织信低代码首创“企业级”低代码概念&#xff0c;定位服务企业数字化升级战略。 经历了全年14个大版本的升级&#xff0c;下面就来细数一下2023的重大功能更新&#xff01; 1、组件设计器 织信团队凭借丰富的项目实施经验和深入客户需求理解&#xff0c;重…

vue3 构建项目

一.使用vite构建&#xff1a; npm init vitelatest 项目名称 构建的项目模板 进入项目 cd 项目名称 安装项目依赖包 npm install 启动项目 npm run dev 二.使用vue脚手架构建&#xff1a; npm init vuelatest 后续基本差不多

Win11系统安装安卓子系统教程

随着Win11系统的不断普及&#xff0c;以及硬件设备的更新换代&#xff0c;我相信很多同学都已经更新并使用到了最新的Win11系统。那么&#xff0c;Win11系统最受期待的功能“Windows Subsystem for Android”&#xff08;简称WSA&#xff09;&#xff0c;即《安卓子系统》。他可…

高马步和四平马步总结

目录 一.高马步武当袁师懋视频讲解高马步要点总结其它零散总结 二.四平马步先开胯&#xff0c;开胯的方法方法总结参考文章 三.站桩问题总结站桩时脚部灼烧感 四.记录2024 1.17 站桩突破25分钟2024年 3.1 晚上尝试四平马步&#xff0c;突破10分钟 站桩脚趾要抓地吗站桩文章 一.…

[AutoSar]BSW_Com03 DBC详解 (一)

目录 关键词平台说明一、DBC 定义1.1 相关工具 二、主要组成部分介绍2.1 Networks2.2 ECUs2.3 Network nodes2.4 messages2.5 signal2.6 Value Tables 三、主要组成部分关系图 关键词 嵌入式、C语言、autosar、OS、BSW 平台说明 项目ValueOSautosar OSautosar厂商vector &am…

[Vulnhub]靶场 Web Machine(N7)

kali:192.168.56.104 主机探测: arp-scan -l 靶机ip:192.168.56.104 端口扫描 nmap -p- 192.168.56.106 看一下web 目录扫描 gobuster dir -u http://192.168.56.106 -x html,txt,php,bak,zip --wordlist/usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt exp…

将仓库A中的部分提交迁移到仓库B中

结论&#xff1a; 使用git format-patchgit am即可实现 使用场景&#xff1a; 例如仓库A这里有5个提交记录&#xff0c;commitid1, commitid2, commitid3, commitid4&#xff0c;commitid5 仓库B想用仓库A中提交的代码&#xff0c;手动改比较慢&#xff0c;当改动较多的时候…

Vuepress的使用

介绍 将markdown静态资源转换成html。 动态资源的转换还有很多&#xff0c;为什么要使用Vuepress&#xff1f; 目录分析 项目配置 详情 具体配置请看文档 插件配置 vuepress-theme-vdoing 主题插件 npm install vuepress-theme-vdoing -D先安装依赖配置主题 使用vuep…

Linux理解

VMware安装Linux安装 目录 VMware安装Linux安装 1.1 什么是Linux 1.2 为什么要学Linux 1.3 学完Linux能干什么 2.1 主流操作系统 2.2 Linux系统版本 VMware安装Linux安装 1.1 什么是Linux Linux是一套免费使用和自由传播的操作系统。 1.2 为什么要学Linux 1). 企业用人…

从预训练到通用智能(AGI)的观察和思考

1.预训练词向量 预训练词向量&#xff08;Pre-trained Word Embeddings&#xff09;是指通过无监督学习方法预先训练好的词与向量之间的映射关系。这些向量通常具有高维稠密特征&#xff0c;能够捕捉词语间的语义和语法相似性。最著名的预训练词向量包括Google的Word2Vec&#…

使用 Haproxy 搭建Web群集

Haproxy是目前比较流行的一种群集调度工具&#xff0c;同类群集调度工具有很多&#xff0c;如LVS 和Nginx。相比较而言&#xff0c;LVS.牲能最好&#xff0e;但是搭建相对复杂:Nginx的upstream模块支持群集功能&#xff0e;但是对群集节点健康检查功能不强&#xff0c;性能没有…

SpringBoot源码解读与原理分析(三十三)SpringBoot整合JDBC(二)声明式事务的生效原理和控制流程

文章目录 前言10.3 声明式事务的生效原理10.3.1 TransactionAutoConfiguration10.3.2 TransactionManagementConfigurationSelector10.3.3 AutoProxyRegistrar10.3.4 InfrastructureAdvisorAutoProxyCreator10.3.5 ProxyTransactionManagementConfiguration10.3.5.1 Transactio…