【算法学习】斐波那契数列模型-动态规划

前言

        我在算法学习过程中,针对斐波那契数列模型的动态规划的例题进行了一个整理,并且根据标准且可靠一点的动态规划解题思路进行求解类似的动归问题,来达到学习和今后复习的必要。

        所谓的斐波那契数列模型,即当前状态的值等于前两种状态的值之和。下面的例题的动态规划递推式都是类似的形式。

一、动态规划解题流程

        针对于动态规划类似的题目,一般有如下的解题套路:

1.确定状态表示

        通常状态我们可以理解为dp表中的每一个值。

        此时就跟经验和题目要求相关了。在一维的线性递推模式下(一维数组),一般经验有dp[i]的第i个表示:

1.从第i个开始....

2.到第i个结束...

        ... 省略的根据题目情况而定,而一般定下的也和我们返回的结果有关。

        比如求解上楼梯的方式问题,dp[i]可以表示上第i阶楼梯时有多少种方式(到第i个结束);或者从第i阶楼梯上到顶层楼梯有多少种方式(从第i个开始)。

        实际上,也需要我们在分析问题的过程中,发现重复子问题的过程。

2.建立状态转移方程

        这一步是核心的一步也是最难的一步。

        动态规划基本上解题是基于dp表的,那么对dp表中的状态赋值需要靠建立的转移方程求解。对此一般存在一个基本的思想:dp[i] 的值可以由最近的值如何求解出来?根据题目上的条件

        一般的经验也是以i位置的状态,最近的一步划分问题。

3.初始化

        一般状态转移方程中存在让dp表越界的机会,初始化就是为了防止填dp表越界

        基本上完成上面1、2步后,接下来的几步就非常简单了。

4.填表顺序

        根据状态转移方程,存在一定的填表顺序,比如斐波那契数列模型,我们每次求当前值时需要知道前面两个值,所以需要从左向右进行赋值。

        根据题目和状态定义,灵活的调整填表顺序。

5.返回值

        一般返回值就是题目最终要的结果,通常就是返回dp表中的某个状态值。

二、例题1-第n个泰波那契数

在我的上一篇博客中详细提过~

【算法学习】第N个泰波那契数-CSDN博客

三、例题2-三步问题

题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

解析题目:

        根据动态规划解题思路,我们首先需要确定状态表示。

        我们想要求对应台阶小孩有多少种上楼梯的方式,实际上就是经验所谈的到第i结束...。而这里的... 就是题目中的上楼梯的多少种方式,所以这里可以得到状态表示为:

dp[i] = 小孩上第i阶台阶方式总数。

        然后我们需要确定状态转移方程

        结合我们之前说过的,从对应值的身边最近状态入手,在结合题目思路就可以得出结果了:因为小孩一次可以上1阶、2阶、3阶。那么dp[i] 此时第i阶台阶可以是通过1阶、2阶、3阶上来的。那么可以存在如下的关系:

        而这实际上对应着如下的关系:

状态转移方程:(i > 3)

        dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3] 

        因为存在三种情况,根据分类计数原理就需要将每种情况进行累加,就可以得到当前状态值的求解了。

        其次就需要针对状态转移方程对dp表初始化了(防止越界,比如如果i<3 ,那么i-3<0发生报错)。

        对1、2、3的dp值进行赋值即可。为什么不是0、1、2?因为0表示0级台阶,不存在意义,所以我们从1阶开始计算(后续创建dp表注意到开辟空间为n+1)。

        1阶对应1种上楼梯方式,2阶对应两种(1步1步上,直接上两步),3阶对应4种(0阶(地面)直接三步上来,1阶两步上来,2阶1步上来)。

        填表顺序自然是从左到右,因为我们需要先知道i-1、i-2、i-3的值。

        题目要求我们返回对于n阶楼梯,小孩有多少种上楼梯的方式,那么返回值就是对应dp[n]即可。

编码:

        编码一般遵循:1.创建dp表2.初始化3.填表的操作。根据上述思路一步一步来即可。

        此题注意模的计算。

class Solution {
public:int waysToStep(int n) {if (n < 3) return n;const int M = 1e9 + 7;// 创建dp表vector<int> dp(n + 1);// 初始化dp[1] = 1, dp[2] = 2, dp[3] = 4;// 填表for (int i = 4; i <= n; ++i)dp[i] = ((dp[i - 1] + dp[i - 2]) % M + dp[i - 3]) % M;return dp[n];}
};

        注意上面只是介绍了基本的dp解题过程,其中对于dp表是存在优化的,可以将空间复杂度On降级为O1。 

四、例题3-使用最小花费爬楼梯

题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

解析题目:

        此题简单描述一下定义不同状态解题的区别。

1.定义状态表示:以i结尾

即dp[i] = 到下标为i的台阶的最低费用。

        分析当前状态值如何求解,题目告诉我们在当前i阶花费cost[i]可以向上爬1层或者两层,那么当前i层是之前i-1阶花费钱爬一层或者之前i-2阶花费钱爬两层得到的,即:

状态转移方程

        dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])

        初始化0、1即可。根据题目,可以选择从下标为0和1的台阶开始向上爬,那么dp[0] 和dp[1]都应该为0。

        填表顺序从左到右(先需要知道左边的dp状态值)。

        题目让我们返回到达楼梯顶部的最低花费,即dp[n];

2.定义状态表示:以i开始

即dp[i] = 从下标为i的台阶开始到楼梯顶部的最低费用。

        注意到此时的状态表示发生了变化,那么就近进行分析,因为当前花费cost[i] 我们可以选择爬1层和爬2层,如果爬1层后是最低费用就爬此,否则就爬另外的一个,如此,就有如下的结果:

状态转移方程

        dp[i] = min(dp[i + 1], dp[i + 2]) + cost[i];

        此时由于状态表示发生了变化,根据递推方程,初始化的方式也发生了变化,我们需要从后往前进行赋值,可以发现实际上我们可以直接求得dp[n - 1]和dp[n - 2]的值(此时dp[n]=0不存在意义了,到达顶层可以是最后一级台阶花费爬一步,或者倒数第二级台阶花费爬两步),所以初始化dp[n - 1]、dp[n - 2]即可。dp[n - 1] = cost[n - 2], dp[n - 2] = cost[n - 1]。

        填表顺序为从右到左,我们需要先知道较大下标的值才能求解较小下标的状态值。

        题目让我们返回到达楼梯顶部的最低花费,因为我们可以选择0、或者1开始往上爬,根据dp值只需要两者之间比较较小的花费返回即可:min(dp[0], dp[1]);

        可以看到,我们将状态定义的不同,后续步骤方程基本不一样,所以定义状态按照自己习惯或者合适的场景进行理解。

编码:

1.以i结尾

class Solution {
public:int minCostClimbingStairs(vector<int>& cost) {// 解法1 设置dp表中的状态表示为到第i台阶花费的最低花费int n = cost.size();// 创建dp表vector<int> dp(n + 1);//初始化dp[0] = dp[1] = 0;// 填表for (int i = 2; i <= n; ++i)dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);return dp[n];}
};

2.以i开始

class Solution {
public:int minCostClimbingStairs(vector<int>& cost) {// 解法2 设置dp表中的状态表示为从第i台阶开始到顶部的最低花费总和int n = cost.size();// 创建dp表vector<int> dp(n);// 初始化dp[n - 1] = cost[n - 1], dp[n - 2] = cost[n - 2];// 填表for (int i = n - 3; i >= 0; --i)dp[i] = min(dp[i + 1], dp[i + 2]) + cost[i];return min(dp[0], dp[1]);}
};

五、例题4-解码方法

题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

 

解析题目:

        针对此题,我们可以提出另外的一种优化方式,来优化我们的动态规划编码,使其看的非常整洁。

        状态的定义我们就以习惯的以i结尾...:s中以i下标结尾时解码方法的总数。

dp[i] = s中以i下标结尾时解码方法的总数。

        对于当前状态值得求解,分析题目,数字可以单独被解码,也可以两个结合解码

        对于一个在s中对应i下标得数字,存在单独解码和结合解码两种思路讨论,那么是和前面结合还是后面结合呢?注意看我们的状态定义:是以i下标结尾时的解码方法总数,不包括后面的解码的,所以是和前面的一个数进行结合解码。

        在细分的去划分,存在解码成功和解码失败。单个解码只要大于0解码成功,否则解码失败,结合解码需要大于等于10、小于26解码成功,否则失败(题目要求:06、60类似这种解码失败) 。

        如果单独解码成功,那么实际上就是在以i-1解码方法的基础上,后面添加了个字母,失败的话,那么以i结尾的解码方法总数加上0。

例子:i=2

s="112"

i - 1 = 1的解码方法总数:2

        1、1 aa

        11 k

那么2单解码成功

i此时的解码方法总数为:0+2

        1、1、2 aab

        11、2 kb

        如果结合解码成功,那么实际上就是在i-2解码方法的基础上,后面添加了个字母,失败的话,那么以i结尾的解码方法总数加上0.

例子:i=2

s="112"

i - 2 = 0的解码方法总数:1

        1 a

那么2结合解码成功

i此时的解码方法总数为:0+1

        1、12 aL

        综上,我们可以得到状态转移方程为:

状态转移方程

        dp[i] = (if int(s[i]) > 0: dp[i - 1] else: 0) +

        (if 10 <= int(s[i-1] + s[i]) <= 26: dp[i - 2] else :0)

        得到状态转移方程后我们需要进行初始化

        需要注意此处的初始化,正常情况下我们需要初始化0、1从而满足dp表不越界的情况,对应的求解dp[1]下标结尾的解码方法总数和状态转移方程有些一样,如果像原本那样写在外面会造成代码存在一定的冗余,我们可以将原本的dp数组扩大一个单位,整体右移。这样多出的一个就可以用来初始化s[1]了。

        此时新增的第一位称作虚拟位,一般虚拟位的值为0(一般情况下,要结合实际情况,此题情况就不为0).这样就可以将旧dp[1]繁琐的步骤进行优化。

根据图示,需要注意的是:

            1.虚拟节点内的值,需要保证后续的填表是正确的。(一般情况下是0正确的,但是在此题是错误的,要填1 -分析)
            2.注意下标的映射关系  dp和s对应的关系。 

        此题需要满足优化后dp[2] 为s下下标为1的解码总数,根据递推公式,如果结合解码成功,需要加上i - 2的dp值,也就是此时对应虚拟节点的对应值,不能给0的原因在这里,给0的话,那么此时结合解码就失效了,所以需要加1,此时虚拟节点的值给予1即可。 

        填表顺序就是从左到右了,返回值根据是否优化返回dp[n]或者dp[n-1]表示此串s编码的解码方法总数了。 

编码:

1.初始化不优化

class Solution {
public:int numDecodings(string s) { int n = s.size();// 建立dp表 状态标识:到第i个编码的解码方法总数vector<int> dp(n);// 初始化dp[0] = s[0] != '0';if (n == 1) return dp[0];if ((s[1] - '0') > 0) dp[1] += dp[0]; int tmp = (s[0] - '0') * 10 + (s[1] - '0');if (tmp >= 10 && tmp <= 26) dp[1] += 1;// 填表 递推公式:第i个可以单独编码 dp[i] += dp[i - 1] 第i个和第i-1个可以凑在一起编码 dp[i] += dp[i - 2]for (int i = 2; i < n; ++i){if (s[i] - '0' > 0) dp[i] += dp[i - 1];tmp = (s[i - 1] - '0') * 10 + s[i] - '0';if (tmp >= 10 && tmp <= 26) dp[i] += dp[i - 2];}return dp[n - 1];
};

        可以看到初始化有点繁琐不简洁,而下面优化后的方案就非常简洁了。 

2.初始化优化

class Solution {
public:int numDecodings(string s) { int n = s.size();// 建立dp表 状态标识:到第i个编码的解码方法总数vector<int> dp(n + 1);// 初始化 优化初始化dp[0] = 1;dp[1] = s[0] != '0';if (n == 1) return dp[1];// 填表 递推公式:第i个可以单独编码 dp[i] += dp[i - 1] 第i个和第i-1个可以凑在一起编码 dp[i] += dp[i - 2]for (int i = 2; i <= n; ++i){if (s[i - 1] - '0' > 0) dp[i] += dp[i - 1];int tmp = (s[i - 2] - '0') * 10 + s[i - 1] - '0';if (tmp >= 10 && tmp <= 26) dp[i] += dp[i - 2];}return dp[n];}
};

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

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

相关文章

每日一练2023.12.25——验证身份【PTA】

题目链接 &#xff1a;验证身份 题目要求&#xff1a; 一个合法的身份证号码由17位地区、日期编号和顺序编号加1位校验码组成。校验码的计算规则如下&#xff1a; 首先对前17位数字加权求和&#xff0c;权重分配为&#xff1a;{7&#xff0c;9&#xff0c;10&#xff0c;5&a…

图片转excel:“保留数字格式”在什么场景下该勾

保留数字格式是什么意思呢&#xff1f;顾名思义&#xff0c;就是将转出来的数字保留为数字格式&#xff0c;而不是文本格式。我们知道&#xff0c;OCR程序将图片上的文字识别为电脑可编辑的文字后&#xff0c;如果导入到excel不加处理&#xff0c;则单个数字过长的文字就会被ex…

.net6使用Sejil可视化日志

&#xff08;关注博主后&#xff0c;在“粉丝专栏”&#xff0c;可免费阅读此文&#xff09; 之前介绍了这篇.net 5使用LogDashboard_.net 5logdashboard rootpath-CSDN博客 这篇文章将会更加的简单&#xff0c;最终的效果都是可视化日志。 在程序非常庞大的时候&…

Linux bridge开启hairpin模拟测试macvlan vepa模式

看到网上介绍可以通过Linux bridge 开启hairpin方式测试macvlan vepa模式&#xff0c;但是没有找到详细资料。我尝试测试总提示错误信息&#xff0c;无法实现&#xff0c;经过几天的研究&#xff0c;我总算实现模拟测试&#xff0c;记录如下&#xff1a; 参考 1.Linux Macvla…

Flink电商实时数仓(六)

交易域支付成功事务事实表 从topic_db业务数据中筛选支付成功的数据从dwd_trade_order_detail主题中读取订单事实数据、LookUp字典表关联三张表形成支付成功宽表写入 Kafka 支付成功主题 执行步骤 设置ttl&#xff0c;通过Interval join实现左右流的状态管理获取下单明细数据…

LED驱动电源

LED驱动电源 常用电子元器件 TB62726AFG LED SOP-24 文章目录 LED驱动电源前言一、LED驱动电源是什么二、TB62726AFG LED SOP-24总结 前言 LED驱动电源可以根据应用需求采用不同的输入和输出电源类型、电源转换拓扑、调光方式等。常见的LED驱动电源类型包括恒流驱动电源、恒…

c# OpenCvSharp 检测(斑点检测、边缘检测、轮廓检测)(五)

在C#中使用OpenCV进行图像处理时&#xff0c;可以使用不同的算法和函数来实现斑点检测、边缘检测和轮廓检测。 斑点检测边缘检测轮廓检测 一、斑点检测&#xff08;Blob&#xff09; 斑点检测是指在图像中找到明亮或暗的小区域&#xff08;通常表示为斑点&#xff09;&#…

数据智慧:C#中编程实现自定义计算的Excel数据透视表

前言 数据透视表&#xff08;Pivot Table&#xff09;是一种数据分析工具&#xff0c;通常用于对大量数据进行汇总、分析和展示。它可以帮助用户从原始数据中提取关键信息、发现模式和趋势&#xff0c;并以可视化的方式呈现。 在数据透视表中&#xff0c;数据分析师通常希望进…

doris数据模型,06-Aggregate(聚合模型)

聚合模型的特点 将表中的列分为Key和Value。 Key是数据的维度列&#xff0c;比如时间&#xff0c;地区等等。key相同时会发生聚合。 Value是数据的指标列&#xff0c;比如点击量&#xff0c;花费等等。 每个指标列还会有自己的聚合函数&#xff0c;如&#xff1a;sum&#xff…

【开源】基于JAVA语言的企业项目合同信息系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 合同审批模块2.3 合同签订模块2.4 合同预警模块2.5 数据可视化模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 合同审批表3.2.2 合同签订表3.2.3 合同预警表 四、系统展示五、核心代码5.1 查询合同…

geyser互通服基岩版进不去

Java版需要在服务器安全组开通TCP端口&#xff08;如果有宝塔&#xff0c;也需要开通&#xff09; geyser下载好的安装运行也需要开通端口&#xff0c;但是它是UDP的&#xff08;但是我同时也开启了TCP&#xff0c;可能不需要&#xff1f; Java 版玩家隧道 Java 版玩家使用 T…

使用教程之【SkyWant.[2304]】路由器操作系统,破解移动【Netkeeper】校园网【小白篇】

许多高校目前饱受Netkeeper认证的痛苦&#xff0c;普通路由器无法使用&#xff0c; 教你利用SkyWant的Netkeeper认证软件来使你的SkyWant路由器顺利认证上网&#xff0c;全宿舍又可以合作共赢了&#xff01; 步骤一&#xff1a;正确连接网线&#xff0c;插电开机 正确连接网…

<meta name=“Keywords“ content=““ >、<meta name=“Description“ content=““ > 等用法解释

今天在看网站代码&#xff0c;发现类似<meta name"Keywords" content"" >、<meta name"Description" content"" >这样的写法&#xff0c;不知道具体代表什么意思&#xff0c;于是上网搜了一下&#xff0c;下面是在网上找到…

计算机组成原理综合6

补码表示&#xff1a; X&#xff1a;1111 1111 1111 1101 Y&#xff1a;1111 1111 1101 1111 Z&#xff1a;0111 1111 1111 1100 转原码表示&#xff1a;从右往左找第一个“1”&#xff0c;左边的所有数值位按位取反 X&#xff1a;1111 1111 1111 1101 1000 0000 00…

学Java的第二天

一、常量 1.值不可以变化的量。 2. 分类&#xff1a; 字符串常量 用双引号括起来的多个字符&#xff0c;可以包含 0、1 或多个&#xff0c;例如 "a" 、 "abc" 、 " 中国 " 整数常量&#xff0c;例如&#xff1a; -10 、 0 、 88 小数常量&…

LeetCode-回文链表(234)

题目描述&#xff1a; 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 因为这一题是受到876题求链表中间节点的启发&#xff0c;所以在这里也加一下。 876.链表的中间结点…

泛微OA xmlrpcServlet接口任意文件读取漏洞(CNVD-2022-43245)

CNVD-2022-43245 泛微e-cology XmlRpcServlet接口处存在任意文件读取漏洞&#xff0c;攻击者可利用漏洞获取敏感信息。 1.漏洞级别 中危 2.影响范围 e-office < 9.5 202201133.漏洞搜索 fofa 搜索 app"泛微-OA&#xff08;e-cology&#xff09;"4.漏洞复现 …

快速安装方式安装开源OpenSIPS和CP控制界面

OpenSIPS是目前世界上主流的两个SIP软交换引擎(其中另外一个是kamailio)或者SIP信令服务器&#xff08;个人认为是比较正确的称谓&#xff09;。关于Opensips的基础和一些参数配置和安装方式笔者在很久以前的历史文档中有非常多的介绍。最近&#xff0c;很多用户使用OpenSIPS软…

Settings中电池选项-Android13

Settings中电池选项-Android13 1、设置中界面2、电池计算2.1 充电时间计算2.1.1 BatteryUsageStats获取2.1.2 BatteryStatsImpl计算 2.2 电池剩余使用时间2.2.1 Estimate获取2.2.2 BatteryStatsImpl计算 3、电池信息来源4、命令模拟* 日志 [电池]Android 9.0 电池未充电与充电字…

[内功修炼]函数栈帧的创建与销毁

文章目录 1:什么是函数栈帧2:理解函数栈帧能解决什么问题呢3:函数栈帧的创建与销毁的解析3.1:什么是栈3.2:认识相关寄存器与汇编指令相关寄存器相关汇编指令 3.3 解析函数栈帧的创建和销毁3.3.1 预备知识3.3.2 详细解析一:调用main函数,为main函数开辟函数栈帧First:push前push…