算法训练day43|动态规划 part05:0-1背包 (LeetCode 1049. 最后一块石头的重量 II、494. 目标和、474.一和零)

news2024/9/25 12:30:00

文章目录

  • 1049. 最后一块石头的重量 II
    • 思路分析
    • 代码实现
  • 494. 目标和
    • 思路分析
    • 动规方法
    • 代码实现
    • 总结思考
  • 474.一和零
    • 思路分析
    • 代码实现
    • 思考总结

var code = "57a5e730-4e5e-43ad-b567-720d69f0371a"

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

题目链接🔥🔥
有一堆石头,每块石头的重量都是正整数。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块石头。返回此石头最小的可能重量。如果没有石头剩下,就返回 0。

示例:
输入:[2,7,4,1,8,1]
输出:1
解释:
组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1],
组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1],
组合 2 和 1,得到 1,所以数组转化为 [1,1,1],
组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。

提示:
1 <= stones.length <= 30
1 <= stones[i] <= 1000

思路分析

本题其实就是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题了。
并且和分割等和子集很像了。

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

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

可以回忆一下01背包中,dp[j]的含义,容量为j的背包,最多可以装的价值为 dp[j]。

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

  1. 确定递推公式

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

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

一些同学可能看到这dp[j - stones[i]] + stones[i]中 又有- stones[i] 又有+stones[i],看着有点晕乎。

大家可以再去看 dp[j]的含义。

  1. dp数组如何初始化
    既然 dp[j]中的j表示容量,那么最大容量(重量)是多少呢,就是所有石头的重量和的一半。
    因为重量都不会是负数,所以dp[j]都初始化为0就可以了,这样在递归公式dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);中dp[j]才不会初始值所覆盖。

  2. 确定遍历顺序
    如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历!

  3. 举例推导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) {
        int sum=0;
        for(int i:stones){
            sum+=i;
        }
        int target=sum/2;
        vector<int> dp(target+1,0);
        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]);
            }
        }
        int result=sum-2*dp[target];
        return result;
    }
};

494. 目标和

题目链接🔥🔥

给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。

返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

示例:
输入:nums: [1, 1, 1, 1, 1], S: 3
输出:5
解释:
-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3
一共有5种方法让最终目标和为3。

提示:
数组非空,且长度不会超过 20 。
初始的数组的和不会超过 1000 。
保证返回的最终结果能被 32 位整数存下

思路分析

这道题目咋眼一看和动态规划背包啥的也没啥关系。

本题要如何使表达式结果为target,

既然为target,那么就一定有 left组合 - right组合 = target。

left + right = sum,而sum是固定的。right = sum - left

公式来了, left - (sum - left) = target 推导出 left = (target + sum)/2 。

target是固定的,sum是固定的,left就可以求出来。

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

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

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

if ((target + sum) % 2 == 1) return 0; // 此时没有方案

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

if (abs(target) > sum) return 0; // 此时没有方案

动规方法

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

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

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

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

  1. 确定递推公式

有哪些来源可以推出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]]

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

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]]推导出来。

  1. 确定遍历顺序

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

  1. 举例推导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 target) {
        int sum=0;
        for(int i:nums) sum+=i;
        if(abs(target)>sum) return 0;
        if((target+sum)%2) return 0;
        int bagsize=(target+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];
    }
};

总结思考

本题还是有点难度,大家也可以记住,在求装满背包有几种方法(仅仅是求个数,不用把所有组合列出来)的情况下,递推公式一般为:

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

后面我们在讲解完全背包的时候,还会用到这个递推公式!


474.一和零

题目链接🔥🔥
给你一个二进制字符串数组 strs 和两个整数 m 和 n 。
请你找出并返回 strs 的最大子集的大小,该子集中 最多 有 m 个 0 和 n 个 1 。
如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。

示例 :
输入:strs = [“10”, “0001”, “111001”, “1”, “0”], m = 5, n = 3
输出:4
解释:最多有 5 个 0 和 3 个 1 的最大子集是 {“10”,“0001”,“1”,“0”} ,因此答案是 4 。 其他满足题意但较小的子集包括 {“0001”,“1”} 和 {“10”,“1”,“0”} 。{“111001”} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。

示例 2:
输入:strs = [“10”, “0”, “1”], m = 1, n = 1
输出:2
解释:最大的子集是 {“0”, “1”} ,所以答案是 2 。

提示:
1 <= strs.length <= 600
1 <= strs[i].length <= 100
strs[i] 仅由 ‘0’ 和 ‘1’ 组成
1 <= m, n <= 100

思路分析

多重背包是每个物品,数量不同的情况。

本题中strs 数组里的元素就是物品,每个物品都是一个!

而m 和 n相当于是一个背包,两个维度的背包。本题其实还是01背包问题
确定dp数组(dp table)以及下标的含义
dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][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);

此时大家可以回想一下01背包的递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

对比一下就会发现,字符串的zeroNum和oneNum相当于物品的重量(weight[i]),字符串本身的个数相当于物品的价值(value[i])。

这就是一个典型的01背包! 只不过物品的重量有了两个维度而已。

dp数组如何初始化
在动态规划:关于01背包问题,你该了解这些!(滚动数组) (opens new window)中已经讲解了,01背包的dp数组初始化为0就可以。

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

确定遍历顺序
在动态规划:关于01背包问题,你该了解这些!(滚动数组) (opens new window)中,我们讲到了01背包为什么一定是外层for循环遍历物品,内层for循环遍历背包容量且从后向前遍历!

那么本题也是,物品就是strs里的字符串,背包容量就是题目描述中的m和n。

有同学可能想,那个遍历背包容量的两层for循环先后循序有没有什么讲究?

没讲究,都是物品重量的一个维度,先遍历哪个都行!

举例推导dp数组

在这里插入图片描述

代码实现

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        vector<vector<int>> dp(m+1,vector<int> (n+1,0));
        for(string str:strs){ // 遍历物品
            int zeronums=0,onenums=0;
            for(char c:str){
                if(c=='0') zeronums++;
                else onenums++;
            }
            for(int i=m;i>=zeronums;i--){  // 遍历背包容量且从后向前遍历!
                for(int j=n;j>=onenums;j--){
                    dp[i][j]=max(dp[i][j],dp[i-zeronums][j-onenums]+1);
                }
            }
        }
        return dp[m][n];
    }
};

思考总结

此时我们讲解了0-1背包的多种应用,

纯0-1背包 是求 给定背包容量 装满背包 的最大价值是多少。
416. 分割等和子集 是求 给定背包容量,能不能装满这个背包。
1049. 最后一块石头的重量 II 是求 给定背包容量,尽可能装,最多能装多少
494. 目标和是求 给定背包容量,装满背包有多少种方法。
本题是求 给定背包容量,装满背包最多有多少个物品。


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

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

相关文章

泽众TestOne自动化测试平台,挡板测试(Mock测试)上线了!!

什么是挡板测试&#xff08;Mock测试&#xff09;&#xff1f; 主要应对与某些不容易构造或者不容易获取的对象以及暂时没有开发完成的对象&#xff0c;设计一个虚拟的对象&#xff0c;配置测试需求的业务数据&#xff0c;完成测试业务。 TestOne是泽众软件自主研发的一体化测…

静态函数(static)-> static 与 const

一.静态函数 静态函数&#xff08;Static Function&#xff09;是指在C中使用static关键字声明的函数。它们与普通成员函数和全局函数不同&#xff0c;具有以下特点&#xff1a; 作用域限制&#xff1a;静态函数在类的作用域内&#xff0c;但它们不依赖于类的实例&#xff0c;…

GIF动态表情如何制作?教你一招超简单的gif制作方法

动态gif表情包是如何制作的&#xff1f;gif格式动图作为网络上流行的一种图片格式&#xff0c;可以将多张静态图片变成一张gif动图&#xff0c;能够以生动有趣的方式传递信息。而且制作这种gif动图的方法也非常的简单&#xff0c;只需要使用gif在线制作&#xff08;https://www…

不就是G2O嘛

从零开始一起学习SLAM | 理解图优化&#xff0c;一步步带你看懂g2o代码 SLAM的后端一般分为两种处理方法&#xff0c;一种是以扩展卡尔曼滤波&#xff08;EKF&#xff09;为代表的滤波方法&#xff0c;一种是以图优化为代表的非线性优化方法。不过&#xff0c;目前SLAM研究的主…

Oracle数据库开发者工具

和开发者相关的数据库特性&#xff0c;功能与工具列举如下&#xff0c;但不限于以下。因为Oracle数据库中的许多功能其实都间接的和开发者发生关系&#xff0c;如Oracle高级安全选件中的透明数据加密&#xff0c;数据编辑。Oracle Spatial and Graph&#xff08;地理空间与图&a…

ansible搭建

一&#xff0c;ansible是一种由Python开发的自动化运维工具&#xff0c;集合了众多运维工具&#xff08;puppet、cfengine、chef、func、fabric&#xff09;的优点&#xff0c;实现了批量系统配置、批量程序部署、批量运行命令等功能 二&#xff0c;特点 * 部署简单 * **默认…

数据结构入门-13-图

文章目录 一、图的概述1.1 图论的作用1.2 图的分类1.2.1 无向图1.2.2 有向图1.2.3 无权图1.2.4 有劝图 1.3 图的基本概念 二、树的基本表示2.1 邻接矩阵2.1.1 邻接矩阵 表示图2.1.2 邻接矩阵的复杂度 2.2 邻接表2.2.1 邻接表的复杂度2.2.2 邻接表By哈希表 三、图的深度优先遍历…

LLM文章阅读:Baichuan 2 干货

如有转载&#xff0c;请注明出处。欢迎关注微信公众号&#xff1a;低调奋进。打算开始写LLM系列文章&#xff0c;主要从数据、训练框架、对齐等方面进行LLM整理。 Baichuan 2: Open Large-scale Language Models 原始文章链接 https://cdn.baichuan-ai.com/paper/Baichuan2-…

Element Plus table formatter函数返回html内容

查看 Element Plus table formatter 支持返回 类型为string 和 VNode对象&#xff1b; 若依全局直接用h函数&#xff0c;无需引用 下面普通基本用法&#xff1a;在Element Plus中&#xff0c;你可以使用自定义的formatter函数来返回VNode对象&#xff0c;从而实现更灵活的自定…

FasterNet(PConv)paper笔记(CVPR2023)

论文&#xff1a;Run, Don’t Walk: Chasing Higher FLOPS for Faster Neural Networks 先熟悉两个概念&#xff1a;FLOPS和FLOPs&#xff08;s一个大写一个小写&#xff09; FLOPS: FLoating point Operations Per Second的缩写&#xff0c;即每秒浮点运算次数&#xff0c;或…

Pytorch实现鸟类品种分类识别(含训练代码和鸟类数据集)

Pytorch实现鸟类品种分类识别(含训练代码和鸟类数据集) 目录 Pytorch实现鸟类品种分类识别(含训练代码和鸟类数据集) 1. 前言 2. 鸟类数据集 &#xff08;1&#xff09;Bird-Dataset26 &#xff08;2&#xff09;自定义数据集 3. 鸟类分类识别模型训练 &#xff08;1&a…

核心实验13合集_vlan mapping 和QinQ_ENSP

项目场景一&#xff1a; 核心实验13合集-1_vlan高级配置_ENSP vlan mapping vlan转换 将用户端发来的vlan30-31的标签全部转换成vlan100向上发送 相关知识点&#xff1a; 定义: VLAN Mapping通过修改报文携带的VLAN Tag来实现不同VLAN的相互映 射。 目的: 在某些场景中&#xf…

c语言数组的用法

c语言数组的用法如下&#xff1a; 一维数组的定义方式 在C语言中使用数组必须先进行定义。一维数组的定义方式为&#xff1a; 类型说明符 数组名 [常量表达式]; 其中&#xff0c;类型说明符是任一种基本数据类型或构造数据类型。数组名是用户定义的数组标识符。方括号中的常量表…

原生js之dom表单改变和鼠标常用事件

那么好,本次我们聊聊表单改变时如何利用onchange方法来触发input改变事件以及鼠标常用的滑入滑出,点击down和点击up事件. 关于onchange方法 onchange方法在鼠标输入完后点击任何非输入框位置时触发.触发时即可改变原有输入框的值. out 、leave、over、down、up鼠标方法 当用…

YOLOV7改进-空洞卷积+共享权重的Scale-Aware RFE

代码 1、先把文件复制到common.py中 2、yolo.py添加类名 3、下半部分进行添加修改 4、cfg-training&#xff1a;新建配置文件 加了一行&#xff0c;后面对于序号1 5、这里选择12层替代

软件第三方测评机构简析:良好的测试环境对软件产品起到的作用

近年来&#xff0c;软件行业发展迅速&#xff0c;软件产品的质量成为用户关注的焦点。而软件的质量评估往往需要依赖专业的第三方测评机构&#xff0c;为了更好地了解软件测试环境对产品质量的重要性&#xff0c;小编整理了以下简析&#xff1a; 一、良好的测试环境对软件产品…

Redis是单线程Or多线程?单线程为什么反而快?

0. 从什么角度看是单线程or多线程 从总体角度来&#xff0c;redis是单线程的&#xff1a; Redis 单线程指的是&#xff1a; 「接收客户端请求->解析请求 ->进行数据读写等操作->发送数据给客户端」 这个过程是由一个线程&#xff08;主线程&#xff09;来完成的…

【Leetcode刷题】哈希

本篇文章为 LeetCode 哈希模块的刷题笔记&#xff0c;仅供参考。 哈希表是一种使用哈希函数组织数据&#xff0c;以支持快速插入和搜索的数据结构。哈希表通过哈希函数通过将任意类型的数据映射到固定大小的数据&#xff0c;以实现快速查找和存储数据。C 中的无序容器 unorder…

音视频编码格式-AAC ADT

1408(16进制) : 0001 0100 0000 1000 audioObjectType为 00010 , 即 2&#xff0c; profie (audioObjectType -1 ) AAC LC samplingFrequencyIndex为 1000 , 即 8 , 对应的采样频率为 16000 channelConfiguration为 0001 , 表示channel数量为1

10、哈希函数与哈希表

哈希函数 出现次数最多的 32G 小文件方法&#xff1a;利用哈希函数在种类上均分 设计RandomPool结构 设计一种结构&#xff0c;在该结构中有如下三个功能: insert(key):将某个key加入到该结构&#xff0c;做到不重复加入 delete(key):将原本在结构中的某个key移除 getRando…