代码随想录算法训练营DAY43|C++动态规划Part5|1049.最后一块石头的重量II、494.目标和、474.一和零

文章目录

  • 1049.最后一块石头的重量II
    • 思路
    • CPP代码
  • 494.目标和
    • 回溯算法
    • 抽象成01背包问题
    • CPP代码
    • 本题总结
  • 474.一和零
    • 思路
    • CPP代码

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

力扣题目链接

文章链接:1049.最后一块石头的重量II

视频链接:这个背包最多能装多少?LeetCode:1049.最后一块石头的重量II

状态:想破脑袋想不出来怎么抽象成背包问题
看完思路之后的状态:也不用想破脑袋

把这些石头尽可能得分成两堆,如果这两堆石头重量相似的话,相撞之后所剩的值就是最小值。

思路

正如上文所说的思路,本题基本就能解出来了(这能想到?)

举例[2, 7, 4, 1, 8, 1]。所有石头重量之和为sum=23->sum/2=11,也就是说,每一堆的重量应该是11,显然我们能凑成11的石头堆([2, 8, 1]),另一堆是12,相撞之后为1

如果是[31, 26, 33, 21, 40]sum=151->sum/2=75,每堆石头重量最好是75(也就是背包容量),然而,该例子我们对多也只能装[33, 40]为73,也就是装不满这个石头堆,另一堆就是78,相撞之后为5

现在能懂吗!现在本题就变得相当简单了。

现在我们重新定义本题:(拿stones=[31, 26, 33, 21, 40]举例)

背包的bagweight=sum/2,物品个数有5个,velue=[31, 26, 33, 21, 40]weight=value=[31, 26, 33, 21, 40]

  • dp含义:

用一维dp数组dp[j],其表示背包重量为j时,任选0-i的物品的最高价值。(这里看不懂推荐阅读文章:0-1背包理论基础之滚动数组(二)

  • 递推公式:

dp[j]=max(dp[j], dp[j-weight[i]]+value[i])

  • 初始化:

dp[j]中的j表示容量,那么最大容量(重量)是多少呢,之前提到过,就是所有石头重量和的一半

也就是先遍历一遍石头,计算出石头总重量 然后除以2,得到dp数组的大小。

当然了,为了尽可能提高效率,我们可以直接把dp数组开到题目所给范围的最大值。

提示中给出 1 < = s t o n e s . l e n g t h < = 30 1 <= stones.length <= 30 1<=stones.length<=30 1 < = s t o n e s [ i ] < = 1000 1 <= stones[i] <= 1000 1<=stones[i]<=1000,所以最大重量就是30 * 1000

所以dp数组开到15000大小就可以了。

vector<int> dp(15001, 0);
  • 遍历顺序:

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

  • 打印:

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

  • 后续处理
    之前我们已经拿到过背包的最大容量了bagweight = sum / 2,等我们装满这个背包后,石头堆已经被我们分成了两部分,一部分已经被我们装进了背包里,为dp[bagweight],另一部分就是sum - dp[bagweight]
    把这两堆石头的总容量对撞:
return (sum-dp[bagweight]) - dp[bagweight];

CPP代码

class Solution {
public:int lastStoneWeightII(vector<int>& stones) {//计算石头堆一共有多重int sum = accumulate(stones.begin(), stones.end(), 0);//分成两半后,我们背包的最大容量int bagweight = sum / 2;vector<int> dp(bagweight + 1, 0);//开始装石头for (int i = 0; i < stones.size(); i++) {for (int j = bagweight; j >= stones[i]; --j) {dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);}}//装完石头后就被分成两堆了return (sum - dp[bagweight]) - dp[bagweight];}
};

494.目标和

力扣题目链接

文章链接:494.目标和

视频链接:装满背包有多少种方法?| LeetCode:494.目标和

状态:本题很重要,几乎奠定了后面求背包问题之组合的相关问题

回溯算法

其实很直接就能想到回溯算法,也就是把本题转变为组合总和问题,只不过确实会超时。

class Solution {
private:vector<vector<int>> result;vector<int> path;void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {if (sum == target) {result.push_back(path);}// 如果 sum + candidates[i] > target 就终止遍历for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {sum += candidates[i];path.push_back(candidates[i]);backtracking(candidates, target, sum, i + 1);sum -= candidates[i];path.pop_back();}}
public:int findTargetSumWays(vector<int>& nums, int S) {int sum = 0;for (int i = 0; i < nums.size(); i++) sum += nums[i];if (S > sum) return 0; // 此时没有方案if ((S + sum) % 2) return 0; // 此时没有方案,两个int相加的时候要各位小心数值溢出的问题int bagSize = (S + sum) / 2; // 转变为组合总和问题,bagsize就是要求的和// 以下为回溯法代码result.clear();path.clear();sort(nums.begin(), nums.end()); // 需要排序backtracking(nums, bagSize, 0, 0);return result.size();}
};

抽象成01背包问题

还记得我们之前做过的1049.最后一块石头的重量II和416.分割等和子集都是将数组拆分成了两部分,那么本题是不是也可以拆分成两部分呢?

对于给定数组nums和整数target,既然题目要求在其中插入正负号,也就是说把数组分成了总和为left的数组和总和为right的数组,其中left-right=target。再一个,我们有left+right=sum
l e f t − r i g h t = t a r g e t left-right=target leftright=target
l e f t + r i g h t = s u m left+right=sum left+right=sum

既然targetsum都是固定数值,可以推导出

l e f t − ( s u m − l e f t ) = t a r g e t left-(sum-left)=target left(sumleft)=target
l e f t = ( t a r g e t + s u m ) / 2 left = (target + sum)/2 left=(target+sum)/2

很明显,本题中我们就是在集合nums中找出和为left的组合,背包的容量为(target+sum)/2

既然出现了除法,那么我们就必须考虑向下取整对结果有没有影响。

首先如果(target+sum)/2不能被2整除,说明left作为背包容量竟然是一个小数!所以本题是无解的;如果target的绝对值大于sum,本题也是无解的。

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

综上,我们可以安心把物品放入背包了!

  • 确定dp数组以及下标含义:这里仍然使用一维dp数组,具体可以看文章0-1背包理论基础之滚动数组(二)。

dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法(这里的最大背包容量为我们之前提到的(target+sum)/2

  • 确定递推公式

本题跟之前不一样,之前都是求容量为j的背包,最多能装多少。本题则是装满有几种方法。其实这就是一个组合问题了

我们对dp[j]对定义即,填满jdp[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[j] += dp[j - nums[i]]

  • dp数组如何初始化

do[0]肯定是要初始化为1的,然后我们dp[j]( j > 0 j>0 j>0),他依赖于前面的dp[j - nums[i]],所以后面的全部要初始化为0

vector<int> dp(bagSize + 1, 0);
dp[0] = 1;
  • 确定遍历顺序

在0-1背包理论基础之滚动数组(二)中,我们讲过对于01背包问题一维dp的遍历,nums放在外循环,target在内循环,且内循环倒序。

  • 推导dp数组

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

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

dp数组状态变化如下:

CPP代码

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];}
};
时间复杂度:O(n × m),n为正数个数,m为背包容量
空间复杂度:O(m),m为背包容量

本题总结

在求装满背包有几种方法的情况下,递推公式一般为:

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

474.一和零

力扣题目链接

文章链接:474.一和零

视频链接:装满这个背包最多用多少个物品?| LeetCode:474.一和零

状态:我觉得是01背包里最难想到的一个,但是很有意思!

题目中要求m个零,n个1;

我们把这俩理解成两个容器,那么装满这两个容器最多有多少个元素,就输出多少个。

再更进一步,我们是不是也能一个背包有两个维度,然后来装这些元素。

其中m用来装0n用来装1,所以两个维度的最高容量分别是mn

思路

动规五部曲:

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

本次的背包有两个维度,所以我们必须使用二维的dp数组

dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]

NOTE:
这里最值得关注的就是本题的dp数组中的两个维度,这两个维度应该理解成每一维都是一个背包,我们要同时满足两个背包

  • 确定递推公式

我们当前的dp[i][j]可以由前一个strs里的字符串推导得来,也就是说对于某个字符串0001而言,有zeroNum=3个0,oneNum=1个1。

那么dp[i][j]应该是dp[i - zeroNum][j - oneNum] + 1

所以递推公式总结如下:

dp[i][j]=max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1)

NOTE:

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

字符串的zeroNumoneNum相当于物品的重量(weight[i]),字符串本身的个数相当于物品的价值(value[i])。

  • dp数组的初始化

01背包的dp数组初始化为0就可以。

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

  • 确定遍历顺序

在本题中物品就是strs里的字符串,背包容量是题目描述中的m和n分别代表了dp数组的两个维度

for (string str : strs) { // 遍历物品int oneNum = 0, zeroNum = 0;for (char c : str) {if (c == '0') zeroNum++;else oneNum++;}for (int i = m; i >= zeroNum; i--) { // 遍历背包容量且从后向前遍历!for (int j = n; j >= oneNum; j--) {dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);}}
}
  • 举例推导dp数组

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

CPP代码

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

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

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

相关文章

8 聚类算法

目录 0 背景 1 Kmeans 1.1 聚类数量k的确定 2 DBSCAN 2.1 三个点 2.2 算法流程 3 层次聚类 3.1 过程 4 基于分布的聚类:高斯混合模型 0 背景 聚类算法是一种无监督学习技术&#xff0c;用于将数据集中的数据点划分为不同的组或簇&#xff0c;使得同一组内的数据点彼此相…

nginx缓存清理

背景 昨天打开我的gpt镜像网站&#xff0c;意外发现静态图片资源全都无法获取了 CoCo-AI 一番排查下来&#xff0c;发现是引用的cdn链接失效了 且cdn源是属于七牛云的&#xff0c;且不再维护&#xff0c;于是果断切换到cloudflare export function getEmojiUrl(unified: str…

Qt简单离线音乐播放器

有上传本地音乐文件&#xff0c;播放&#xff0c;暂停&#xff0c;拖拉进度条等功能的播放器。 mainwindow.cpp #include "mainwindow.h" #include "ui_mainwindow.h" #include <QMediaPlayer> #include <QFileDialog> #include <QTime&g…

【Leetcode每日一题】 分治 - 排序数组(难度⭐⭐)(69)

1. 题目解析 题目链接&#xff1a;912. 排序数组 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 归并排序&#xff08;Merge Sort&#xff09;是一种采用“分而治之”&#xff08;Divide and Conquer&#xff09;策略…

数字电路-5路呼叫显示和8路抢答器

本内容涉及两个电路&#xff0c;分别为5路呼叫显示电路和8路抢答器电路&#xff0c;包含Multisim仿真原文件&#xff0c;为掌握FPGA做个铺垫。紫色文字是超链接&#xff0c;点击自动跳转至相关博文。持续更新&#xff0c;原创不易&#xff01; 目录&#xff1a; 一、5路呼叫显…

【百度Apollo】探索自动驾驶:百度Apollo视觉感知模块的实践与创新

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《linux深造日志》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 引入一、百度Apollo视觉感知模块概述二、启动感知模块步骤一&#xff1a;进入 Docker 环境并启动 Dreamview步骤二…

HOOPS Exchange导入数据时如何使用CATIA缓存选项?

1、什么是CATIA缓存选项和CGR文件&#xff1f; CATIA V5默认的工作方式是加载几何图形。加载大型程序集时&#xff0c;这可能会导致性能下降&#xff0c;因为所需的内存很重要。 在这种情况下&#xff0c;我们可能需要使用缓存选项。这将生成仅包含曲面细分数据而不包含几何图…

Docker容器---docker-Consul部署

一、Docker-consul简介 1、概述 consul是google开源的一个使用go语言开发的服务管理软件。支持多数据中心、分布式高可用的、服务发现和配置共享。采用Raft算法&#xff0c;用来保证服务的高可用。内置了服务注册与发现框架、分布一致性协议实现、健康检查、Key/Value存储、多…

【Hadoop】-Hive客户端:HiveServer2 Beeline 与DataGrip DBeaver[14]

HiveServer2 & Beeline 一、HiveServer2服务 在启动Hive的时候&#xff0c;除了必备的Metastore服务外&#xff0c;我们前面提过有2种方式使用Hive&#xff1a; 方式1&#xff1a; bin/hive 即Hive的Shell客户端&#xff0c;可以直接写SQL方式2&#xff1a; bin/hive --…

完美解决AttributeError: module ‘backend_interagg‘ has no attribute ‘FigureCanvas‘

遇到这种错误通常是因为matplotlib的后端配置问题。在某些环境中&#xff0c;尤其是在某些特定的IDE或Jupyter Notebook环境中&#xff0c;可能会因为后端配置不正确而导致错误。错误信息提示 module backend_interagg has no attribute FigureCanvas 意味着当前matplotlib的后…

数智新重庆 | 推进信号升格 打造算力山城

2024年&#xff0c;是实现“十四五”规划目标任务的关键一年&#xff0c;高质量的5G网络、强大的AI能力作为新质生产力的重要组成部分&#xff0c;将有效赋能包括制造业在内的千行万业数字化化、智能化、绿色化转型升级&#xff0c;推动融合应用新业态、新模式蓬勃兴起&#xf…

【javaWeb项目】基于网页形式,通过浏览器访问的java应用程序,就称为javaweb程序

JavaWeb前端 第一章 1、javaWeb是什么 //基于网页形式&#xff0c;通过浏览器访问的java应用程序&#xff0c;就称为javaweb程序2、web程序的分类 //1、静态web程序特点&#xff1a;网页上的内容是固定不变的&#xff0c;不能动态加载&#xff0c;例如web前端//2、动态web程序…

linux 搭建知识库文档系统 mm-wiki

目录 一、前言 二、常用的知识库文档工具 2.1 PingCode 2.2 语雀 2.3 Tettra 2.4 Zoho Wiki 2.5 Helpjuice 2.6 SlimWiki 2.7 Document360 2.8 MM-Wiki 2.9 其他工具补充 三、MM-Wiki 介绍 3.1 什么是MM-Wiki 3.2 MM-Wiki 特点 四、搭建MM-Wiki前置准备 4.1 前置…

【iOS】消息流程分析

文章目录 前言动态类型动态绑定动态语言消息发送objc_msgSendSEL&#xff08;selector&#xff09;IMP&#xff08;implementation&#xff09;IMP高级用法 MethodSEL、IMP、Method总结流程概述 快速查找消息发送快速查找的总结buckets 慢速查找动态方法解析resolveInstanceMet…

用 PyTorch 构建液态神经网络(LNN)

用 PyTorch 构建液态神经网络&#xff08;LNN&#xff09; 文章目录 什么是液态神经网络为什么需要液态神经网络LNN 与 RNN 的区别用 PyTorch 实现 LNNStep 1. 导入必要的库Step 2. 定义网络架构Step 3. 实现 ODE 求解器Step 4. 定义训练逻辑 LNN 的缺陷总结 什么是液态神经网络…

设计模式第二次测试 | 数据库连接池设计(原型模式、创建者模式、适配器模式)

需求中文如下&#xff1a;原本是英文&#xff0c;用百度翻译转换而来 我们需要设计一个工具&#xff0c;它负责创建一个与数据库软件MySQL的连接池。 连接池中有数百个连接可供客户端使用。 所有连接对象都有相同的内容&#xff0c;但它们是不同的对象。 连接对象的创建是资源密…

聊聊 ASP.NET Core 中间件(一):一个简单的中间件例子

前言&#xff1a;什么是中间件 服务器在收到 HTTP 请求后会对用户的请求进行一系列的处理&#xff0c;比如检查请求的身份验证信息、处理请求报文头、检查是否存在对应的服务器端响应缓存、找到和请求对应的控制器类中的操作方法等&#xff0c;当控制器类中的操作方法执行完成…

基于Spring Boot的校园博客系统设计与实现

基于Spring Boot的校园博客系统设计与实现 开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/idea 系统部分展示 系统功能界面图&#xff0c;在系统首页可以查看首页、文…

Apache DolphinScheduler支持Flink吗?

随着大数据技术的快速发展&#xff0c;很多企业开始将Flink引入到生产环境中&#xff0c;以满足日益复杂的数据处理需求。而作为一款企业级的数据调度平台&#xff0c;Apache DolphinScheduler也跟上了时代步伐&#xff0c;推出了对Flink任务类型的支持。 Flink是一个开源的分…

《STM32 HAL库》中断相关函数详尽解析——外部中断服务函数

观前提醒&#xff1a;本文简要回顾了EXTI及NVIC相关知识点&#xff0c;分析了stm32f1系列单片机外部中断回调机制 开始之前&#xff0c;先温习一下有关EXTI和NVIC的知识点 外部中断/事件控制器(EXTI) 对于互联型产品&#xff08;105、107系列&#xff09;&#xff0c;外部中断…