代码随想录算法训练营第三十七天|1049. 最后一块石头的重量 II ,494. 目标和,474.一和零

news2025/1/11 10:17:57

1049. 最后一块石头的重量 II - 力扣(LeetCode)

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

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

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

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

示例 1:

输入:stones = [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],这就是最优值。

思路:第一步:本题如何转换成背包问题;第二步转换成背包问题之后,哪些是重量哪些是价值;

①这里注意到题目中x==y将完全粉碎,完全粉碎相较于x!=y,剩下的石头重量肯定最小,所以从结果来看,将stones分成两堆重量接近甚至相等的石头,碰撞之后会使剩下石头最小甚至没有剩下。

②背包问题中的价值也就是这里石头重量之和,我们要使装石头的重量等于全部石头的一半,遍历才会停止。背包问题中的重量相当于每个石头的重量。

解决:动态规划五步曲

        1.确定dp[i]的含义;

        j表示我们要装j重量的石头,j最大等于目标值;dp[j]表示当前目标是j重量情况下,能满足的最大石头重量和

        这里有点绕,可以举例说明一下,例如有三个石头2,7,4,dp[9]和dp[10]其实都等于2+7,因为如果加上4,重量会超过我们要求的目标值10,实际上dp[9]到dp[12]都等于2+7,但如果目标值是13,dp[13]=2+7+4。

        2.确定递推公式;

        经过上步分析我们已经知道了,dp[j]当前最大的重量其实来自于两种情况:①加上石头i,②不加上石头i,这又和之前题目类似。

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

        3.dp数组初始化;    

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

        vector<int> dp(15001,0)

        4.确定遍历顺序;

        遍历顺序和背包问题一维数组一样,从后向前遍历,防止重复包含。

        5.举例推导dp。

        输入:[2,4,1,1],此时target = (2 + 4 + 1 + 1)/2 = 4

代码:注意返回值是sum减去两个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. 目标和 - 力扣(LeetCode)

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

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

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

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

示例 1:

输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 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
+1 + 1 + 1 + 1 - 1 = 3

思路:如何将本题转换成背包问题

        目标targret由nums里面元素加或者减运算得出,所以我们将结果分为两个集合;一种里面都是加元素,一种都是减元素。

        例如-1 + 1 + 1 + 1 + 1 = 3加法集合里是nums[1]到nums[4],减法集合里是nums[0];

        设加法集合里元素和是x,那减法集合里元素和是sum-x;(sum是nums里全部元素和);

        目标值等于加法集合元素和减去减法集合元素和;target=x-(sum-x)

        最后x=(sum+target)/2,此时问题就转化为,装满容量为x的背包,有几种方法。(这一步很重要!)。这里再解释一下,只要从nums中找到元素使其的和等于x,那么nums中剩下的元素在计算target时减去,最后结果就是target。

解决:动态规划五步曲

        1.确定dp[j]的含义;

        首先确定j,j表示和是j,也就是背包问题里的容量,j从小到x;dp[j]就表示组成和是j的方法数量

        2.确定递推公式;

        如果我们要用若干个元素组成和为j的方案数,那么有两种选择:不选第i个元素或者选第i个元素。如果不选第i个元素,那么原来已经有多少种方案数就不变;如果选第i个元素,那么剩下要组成和为j - nums[i] 的方案数就等于dp[j - nums[i]]。所以两种选择相加就是dp[j]。

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

        3.dp数组初始化;   

        考虑极端情况,如果j=0时候,数组[0],此时方法只有1种,所以dp[0]=1;注意如果数组[0,0,0,0,0],j=0时,dp[0]并不等于1, 此dp[0]非彼dp[0],五个0组成算式等于0的方法是从初始的dp[0]=1累加起来的。没有初始化dp[0]=1,结果都会是0。

        4.确定遍历顺序;

        由于每次更新dp[j]都依赖于之前计算过得dp值(也就是说当前行依赖于上一行),所以我们必须从后往前遍历更新dp值(也就是说从右往左更新),否则会覆盖掉之前需要用到得值。

        5.举例推导dp。

输入:nums: [1, 1, 1, 1, 1], S: 3

bagSize = (S + sum) / 2 = (3 + 5) / 2 = 4

代码:注意目标值大于总和情况和目标值为负数并且小于负总和的情况。

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum=0;
        for(int i=0;i<nums.size();i++){
            sum+=nums[i];
        }
        if(target>sum||(target)<(-sum)) return 0;//目标值大于总和或者小于负总和,不存在
        if ((target + sum) % 2 == 1) return 0; // 此时没有方案
        int x=(sum+target)/2;
        vector<int> dp(x+1,0);//这里需要x+1,因为0也要算
        dp[0]=1;
        for(int i=0;i<nums.size();i++){
            for(int j=x;j>=nums[i];j--){
                dp[j]+=dp[j-nums[i]];
            }
        }
        return dp[x];
    }
};

474. 一和零 - 力扣(LeetCode)

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

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

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

示例 1:

输入: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 。

思路:转换成01背包问题,搞清楚谁是背包,谁是物品,最大子集在背包问题中是什么?

        物品:每个元素都有1和0,1和0的个数就代表该物品属性;

        背包:我们知道物品是元素,那元素要放在子集里,所以子集是背包,以往我们都知道背包有容量,这里子集也“容量”,变成了m和n,也就是说装的元素含有1的个数不超过n,有1的个数不超过m;容量变成了n和m,那背包所装物品的价值就是子集的长度,问题也就是求子集的最大长度转换成了背包所装物品的最大价值

解决:动态规划五步曲

        1.确定dp[i][j]的含义;

        i这里指0的数量,j这里指1的数量,最后dp[i][j]就是表示子集的元素个数。

        2.确定递推公式;

        获得dp[i][j]同样有两种方式,加入当前元素,dp[i-zero][j-one]+1,或者不加dp[i][j];zero代表0的个数,one代表1的个数,并且是指当前遍历的的元素。

        dp[i][j]=max(dp[i][j],dp[i-zero][j-one]+1)

        3.dp数组初始化; 

         没装元素前,0和1个数以及子集长度都是0,初始化就是dp[i][j]=0。

        4.确定遍历顺序;

        还是从后向前遍历,注意这里i和j都需要从后向前遍历,防止元素重复加入导致0和1的个数变大。

        5.举例推导dp。

        以输入:["10","0001","111001","1","0"],m = 3,n = 3为例

        注意这里和二维背包并不相同,i和j都是要考虑的“容量”

代码:

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        vector<vector<int>> dp(m + 1, vector<int> (n + 1, 0)); // 默认初始化0
        for (string str : strs) {
            int zero=0;int one=0;
            for (char c : str) {//统计元素中0和1的个数
                if (c == '0') zero++;
                else one++;
            }
            for (int i = m; i >= zero; i--) { // 遍历背包容量且从后向前遍历!
                for (int j = n; j >= one; j--) {
                    dp[i][j] = max(dp[i][j], dp[i - zero][j - one] + 1);
                }
            }
        }
        return dp[m][n];
    }
};

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

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

相关文章

Java常见算法和lambda

查找算法 public class day11 {public static void main(String[] args) {//基本查找 / 顺序差宅//核心://从0索引开始挨个往后查找//需求:定义一个方法利用基本查找 查询某个元素是否存在//数据如下:{131,127,147,81,103,23,7,79}int[] arr{131,127,147,81,103,23,7,79};int…

全志H6-ARMLinux第1天:全志概述、刷机登陆、官方外设库、蜂鸣器、超声波测距

1. 全志H616课程概述&#xff08;456.01&#xff09; 1.1 为什么学 学习目标依然是Linux系统&#xff0c;平台是ARM架构 蜂巢快递柜&#xff0c;配送机器人&#xff0c;这些应用场景用 C51、STM32 单片机无法实现第三方介入库的局限性&#xff0c;比如刷脸支付和公交车收费设…

超级好用的IDEA插件推荐

IDEA是一款功能强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c;它可以帮助开发人员更加高效地编写、调试和部署软件应用程序。我们在编写完接口代码后需要进行接口调试等操作&#xff0c;一般需要打开额外的调试工具。 今天给大家介绍一款IDEA插件&#xff1a;Api…

Vulnhub项目:EMPIRE: BREAKOUT

一、靶机地址 靶机地址&#xff1a;Empire: Breakout ~ VulnHub 靶机介绍&#xff1a; 该靶机被定义为简单&#xff0c;但是如果没有找到&#xff0c;那就难度成中等了&#xff01; 二、渗透过程 老三样&#xff0c;发现目标&#xff0c;这里用 arp-scan 确定靶机 ip&#…

网络通信的流程,浏览器地址?

1.没有交换机的通信 在一个机房内,有两台电脑相互需要通信 假设现在有三台电脑: 随着电脑的增加,线的数量也在增加,因此显得很臃肿&#xff0c;次数交换机诞生&#xff0c;很好的解决了这一方面&#xff0c; 交换机不需要进行多条线的连接: 通过给设备分配,ip地址来实现局域网…

麻辣香锅病毒处置

1. 前言 今天早上正忙着&#xff0c;上级领导通知&#xff0c;说态势感知发现2023-12-05 18:40:50 主机X.X.93.21遭受攻击&#xff0c;攻击者为X.X.0.7后迅速开展检查&#xff0c;核实确认。 2. 原因分析 这里我从态势感知上看是IP&#xff1a;X.X.93.21去攻击IP&#xff1a;…

springboot 在自定义注解中注入bean,解决注入bean为null的问题

问题&#xff1a; 在我们开发过程中总会遇到比如在某些场合中需要使用service或者mapper等读取数据库&#xff0c;或者某些自动注入bean失效的情况 解决方法&#xff1a; 1.在构造方法中通过工具类获取需要的bean 工具类代码&#xff1a; import org.springframework.beans…

制药企业的设备健康管理系统为何要符合计算机化系统验证CSV?

在制药行业&#xff0c;设备健康管理对于确保生产过程的可靠性和质量至关重要。为了有效管理和监控设备的状态&#xff0c;制药企业常常采用设备健康管理系统。然而&#xff0c;这些系统的可靠性和合规性需要通过计算机化系统验证&#xff08;CSV&#xff09;进行验证。本文将探…

STM32——定时器Timer

定时器工作原理 软件定时 缺点&#xff1a;不精确、占用 CPU 资源 void Delay500ms() //11.0592MHz {unsigned char i, j, k;_nop_();i 4;j 129;k 119;do{do{while (--k);} while (--j);} while (--i); } 使用精准的时基&#xff0c;通过硬件的方式&#xff0c;实现定时功…

消费1000返1500元,买了4罐奶粉倒赚商家2000元?商家亏吗?

大家好&#xff0c;我是微三云胡佳东&#xff0c;一个深耕私域电商模式玩法的互联网人&#xff01;&#xff01; 在当前的全球经济环境下&#xff0c;消费增值的概念正逐渐受到广泛的关注。这一模式的崛起&#xff0c;不仅仅是一种商业模式的创新&#xff0c;更代表着我们对经…

(十五)Flask覆写wsgi_app函数实现自定义中间件

中间件 一、剖析&#xff1a; 在前面讲session部分提到过&#xff1a;请求一进来&#xff0c;Flask会自动调用应用程序对象【Flask(__name__)】的__call__方法&#xff0c;这个方法负责处理请求并返回响应&#xff08;其实如下图&#xff1a;其内部就是wsgi_app方法&#xff…

报表多源关联

报表多源关联 需求背景 在项目中会遇到多种数据展现在一起的报表。例如部分指标在关系型数据库中&#xff0c;部分指标通过restful接口获得到json&#xff0c;然后根据共同的维度关联一起&#xff0c;形成新的数据集。 解决方案 在硕迪报表中有两种方式实现该多源报表&…

UI自动化测试工具的定义及重要性

UI自动化测试工具在现代软件开发中起着不可或缺的作用。它们能够提高测试效率、减少人为错误、提供全面的测试覆盖&#xff0c;并支持持续集成。通过有效使用UI自动化测试工具&#xff0c;开发团队可以提高软件质量&#xff0c;提供更可靠的应用程序&#xff0c;满足用户的需求…

C语言内存函数讲解

目录 文章目录 内存函数针对的数据类型不确定可能是整型数据&#xff0c;字符数据&#xff0c;结构体数据...... memcpy的使用和模拟实现 memcpy的使用 memcpy打印字符数据 memcpy打印整型数据 memcpy的模拟实现 模拟实现的memcpy打印重叠情境 memmove的使用和模拟实现 memm…

vue 实现返回顶部功能-指定盒子滚动区域

vue 实现返回顶部功能-指定盒子滚动区域 html代码css代码返回顶部显示/隐藏返回标志 html代码 <a-icontype"vertical-align-top"class"top"name"back-top"click"backTop"v-if"btnFlag"/>css代码 .top {height: 35px;…

2023年天猫淘宝双12红包口令领取时间是从什么时候开始年终好价节活动?

2023年淘宝双12红包年终好价节活动时间 「领取时间」2023年淘宝年终好价节红包领取时间是从2023年12月8日00:00开始持续到12月12日23:59结束&#xff0c;在活动时间内每天都可以领取一次淘宝2023年终好价节红包&#xff0c;最高可得8888元淘宝超级红包&#xff1b; 「使用时间…

Python实现word自动化

个人网站 文章首发于公众号&#xff1a;小肖学数据分析 介绍 本教程将介绍如何使用Python的python-docx库来自动化Microsoft Word文档的创建和编辑工作&#xff0c;从而提高办公效率和准确性。 前提条件 基本的Python编程知识。 Python环境已安装python-docx库&#xff08;…

MySQL高级--01_1--数据库缓冲池(buffer pool)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 数据库缓冲池(buffer pool)DBMS 会申请占用内存来作为数据缓冲池&#xff0c;在真正访问页面之前&#xff0c;需要把在磁盘上的页缓存到内存中的Buffer Pool 之后才…

使用Inno Setup 打包程序文件 怎么把其中一个文件安装时复制到指定系统文件夹

环境: Inno Setup 6.6 Win10 专业版 问题描述: 使用Inno Setup 打包程序文件 怎么把其中一个文件安装时复制到指定系统文件夹 将文件api-ms-win-shcore-scaling-l1-1-1.dll复制到system32里面 解决方案: 1.由于安全和权限的限制,直接在Inno Setup脚本中复制文件到C:\…

如何使用phpStudy本地快速搭建网站并内网穿透远程访问

文章目录 使用工具1. 本地搭建web网站1.1 下载phpstudy后解压并安装1.2 打开默认站点&#xff0c;测试1.3 下载静态演示站点1.4 打开站点根目录1.5 复制演示站点到站网根目录1.6 在浏览器中&#xff0c;查看演示效果。 2. 将本地web网站发布到公网2.1 安装cpolar内网穿透2.2 映…