【动态规划】斐波那契数列模型

【动态规划】斐波那契数列模型

文章目录

  • 【动态规划】斐波那契数列模型
    • 前言
    • 一、第 N 个泰波那契数
    • 二、三步问题
    • 三、使用最小花费爬楼梯
    • 四、解码方法
    • 总结

前言

​ 我们将深入探讨解决斐波那契数列模型相关问题的解决方法。通过一系列精心挑选的例题,我们将展示如何运用DP的核心原则来解决各种编程挑战,从泰波那契数的计算到楼梯爬升问题,再到解码方法的多样性。每个问题都将通过状态表示、状态转移方程、初始化、填表顺序和返回值的详细分析来解构,旨在提供一个清晰的算法实现框架,帮助读者掌握DP的精髓,并在实际编程中灵活运用。


一、第 N 个泰波那契数

题目链接:第 N 个泰波那契数

泰波那契序列 Tn 定义如下:
T0 = 0, T1 = 1, T2 = 1, 且在 n >= 0 的条件下 Tn + 3 = Tn + Tn + 1 + Tn + 2
给你整数 n,请返回第 n 个泰波那契数 Tn 的值。

算法流程:

  1. 状态表示

由题给出的要求:返回第 n 个泰波那契数 Tn 的值,所以确定 dp[i] 表示:第 i 个泰波那契数的值

  1. 状态转移方程

在这里插入图片描述

对题中给出的递推公式:Tn+3 = Tn + Tn+1 + Tn+2,两边同时令n = n-3时,可得Tn = Tn-3 + Tn-2 + Tn-1,所以我们可以得到状态转移方程:

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

  1. 初始化

由状态转移方程可知,数组下标不能小于0,故 i 的取值最小为 i = 3,所以我们在填表之前需要将 0,1,2 的位置值初始化。由题中给出 T0, T1, T2 的值,即可初始化:dp[0] = 0, dp[1] = dp[2] = 1

  1. 填表顺序

从左往右

  1. 返回值

要求Tn的值,应该返回 dp[n] 的值

示例代码:

int tribonacci(int n) 
{// 注意提前处理边界条件,避免访问越界if (n == 0) { return 0; }if (n == 1 || n == 2) { return 1; }// 滚动数组节约空间size_t a = 0, b = 1, c = 1;size_t result = a + b + c;for (int i = 3; i < n; ++i){a = b;b = c;c = result;result = a + b + c;}return result;
}

二、三步问题

题目链接:三步问题

有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶或3阶。

实现一种方法,计算小孩有多少种上楼梯的方式。

结果可能很大,你需要对结果模1000000007。

提示:

​ n范围在[1, 1000000]之间

算法流程:

  1. 状态表示

dp[i] 表示:到达 i 位置时,一共有多少种方法

  1. 状态转移方程

在这里插入图片描述

由于 dp[i] 表示一共多少中方式可以到 i 位置,且题目规定一次可以上1,2,3个台阶,所以得到状态转移方程:

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

需要注意这里由于题目列出计算出结果的数据量可能过大,需要对结果的可能数作取模运算,所以尽量在三项中每两项相加后就进行一次取模运算,避免数据溢出。即为dp[i] = ((dp[i - 1] + dp[i - 2]) % 1000000007 + dp[i - 3]) % 1000000007

  1. 初始化

从我们的状态转移方程可以看出,方程中 i 的取值最小为3,但是题目中给出的提示:n的取值大于等于1,所以 i 的最小取值应该为4,接着可以通过上面的递推公式陆续推导出其他下标位置的值。那么初始化:dp[1] = 1, dp[2] = 2, dp[3] = 4

  1. 填表顺序

从左往右

  1. 返回值

应该返回 dp[n] 的值

示例代码:

int waysToStep(int n) {// 递推公式 Tn = T<n-1> + T<n-2> + T<n-3>size_t a = 1, b = 2, c = 4;if (n == 1) { return a; }else if (n == 2) { return b; }else if (n == 3) { return c; }size_t result = 0;for (size_t i = 4; i <= n; i++){result = ((a + b) % 1000000007 + c) % 1000000007;a = b;b = c;c = result;}return result;
}

三、使用最小花费爬楼梯

题目链接:使用最小花费爬楼梯

给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。

你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。

请你计算并返回达到楼梯顶部的最低花费。

注意:这里cost[n]对应的每个下标表示的都是楼层,而楼顶需要到达下标为n的位置,即一共 n+1 个位置。

算法流程:

  1. 状态表示

dp[i] 表示:到达 i 位置时的最小花费。(注意:这里i可以取n,所以数组长应该设为n+1)

  1. 状态转移方程

在这里插入图片描述

由题我们得知,每次跨越单位可以是1或2,所以 dp[i] 的值应该是一步前的位置对应的已花费金额+从一步前位置到该位置cost金额两步前的位置对应的已花费金额+从两步前位置到该位置cost金额 相比的最小值

  1. 初始化

从状态转移方程可以看出,我们需要初始化 i = 0 和 i = 1位置的值,由题:dp[0] = dp[1] = 0

  1. 填表顺序

从左往右

  1. 返回值

根据我们前面的推导,数组应该初始化长度为n+1,返回值为 dp[n],而非 dp[n - 1]

示例代码:

int minCostClimbingStairs(vector<int>& cost) {size_t n = cost.size();vector<int> dp(n + 1, 0);// dp[i]表示到第i位置的最小花费// dp[i] = min(dp[i - 1]+cost[i - 1], dp[i - 2]+cost[i - 2])// 1.初始化dp[0] = dp[1] = 0;// 2.填表for (size_t i = 2; i <= n; i++){dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);}return dp[n];
}

四、解码方法

题目链接:解码方法

一条包含字母 A-Z 的消息通过以下映射进行了 编码

'A' -> "1"
'B' -> "2"
...
'Z' -> "26"

解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,"11106" 可以映射为:

  • "AAJF" ,将消息分组为 (1 1 10 6)
  • "KJF" ,将消息分组为 (11 10 6)

注意,消息不能分组为 (1 11 06) ,因为 "06" 不能映射为 "F" ,这是由于 "6""06" 在映射中并不等价。

给你一个只含数字的 非空 字符串 s ,计算并返回 解码 方法的 总数

题目数据保证答案肯定是一个 32 位 的整数。

算法流程:

  1. 状态表示

由于我们需要遍历整个字符串才能做出判断,那么不妨设定以 i 位置为结尾,一共有多少种解码方法,所以:dp[i] 表示:字符串中 [0, i] 区间一共有多少种解码方法

  1. 状态转移方程

根据我们前面分析的 i 位置状态表示,我们在考虑 i 位置时已经将 i - 1 位置的数字纳入考虑范围,因为题中给出的解码条件可以单独解码,也可以和 i - 1 位置联合解码。所以我们不必在分析 i - 1 位置时考虑 (i - 1) + 1 位置的值是否可以与自身进行联合编码,这正是因为在分析 i 位置时自然会分析 i - 1 位置。

关于 i 位置的解码情况,可以分为单独解码和联合解码两种方式:

  • **单独解码:**单独考虑 i 位置上的数可以解码为一个字母。
  • **联合解码:**让 i 位置的数与 i - 1 位置的数结合,解码成一个字母。

<1> 让 i 位置上的数单独解码,存在解码成功与失败

  1. 解码成功:当 i 位置上的数在 [1, 9] 之间的时候,说明 i 位置上的数是可以单独解码的,那么此时 [0, i] 区间上的解码方法应该等于 [0, i - 1] 区间上的解码方法。因为 [0, i - 1] 区间上的所有解码结果,后面填上一个 i 位置解码后的字母就可以了。此时 dp[i] = dp[i - 1]
  2. 解码失败:当 i 位置上的数是 0 的时候,说明 i 位置上的数是不能单独解码的,那么此时 [0, i] 区间上不存在解码方法。因为 i 位置如果单独参与解码,但是解码失败了,那么前面做的努力就全部白费了。此时 dp[i] = 0

<2> 让 i 位置上的数与 i - 1 位置上的数结合解码成一个字母,也存在解码成功和失败

  1. 解码成功:当结合的数在 [10, 26] 之间的时候,说明 [i - 1, i] 两个位置是可以解码成功的,那么此时 [0, i] 区间上的解码方法应该等于 [0, i - 2 ] 区间上的解码方法,原因同上。此时 dp[i] = dp[i - 2]
  2. 解码失败:当结合的数在 [0, 9][27 , 99] 之间的时候,说明两个位置结合后解码失败(这里一定要注意 00 01 02 03 04 … 这几种情况),那么此时 [0, i] 区间上的解码方法就不存在了,原因依旧同上。此时 dp[i] = 0

综上所述:dp[i] 最终的结果应该是上面两两成功与失败的情况下,解码成功的两种情况的累加和。因此可以得到状态转移方程:

在这里插入图片描述

  • 当 s[i] 上的数在 [1, 9] 区间上时:dp[i] += dp[i - 1]
  • 当 s[i - 1] 与 s[i] 上的数结合后,在 [10, 26] 之间的时候: dp[i] += dp[i - 2]
  • 如果上面两种情况都不满足,说明没有解码方法:dp[i] = 0

**注意:**上面为何设定 dp[i] += dp[i-1] (或 dp[i-2] ),究其原因是为了适应 dp[i] 位置被提前初始化为0的情况,同时避免因为对单独解码和联合解码的逻辑处理先后引起 dp[i] 数值不理想的情况。(例如可以单独解码但是不能联合解码,按照上面情况推断的逻辑需要对 dp[i] 先后赋值为 dp[i-1], 0 ,但是显然可以单独编码这里却因为对逻辑判断的顺序导致了 dp[i] 处值被错误置0的情况)

  1. 初始化

由状态转移方程可知,要推导其他位置的值首先需要用到 i - 1, i - 2 位置的dp值,因此需要初始化 dp[0], dp[1] 的值。

对 0,1 位置初始化时,同样需要遵循单独解码与联合解码的要求:

<1> 对 dp[0]:

当 s[0] == ‘0’ 时,没有解码方法,dp[0] = 0

当 s[0] != ‘0’ 时,能单独解码成功,dp[0] = 1

<2> 对 dp[1]:

当 s[1] 在 [1, 9] 之间时,能单独解码,此时 dp[i] += dp[i - 1] (这里dp[0] 的值不论等于1还是0,利用+=刚好可以对应if/else的两种分支对 dp[1] 赋值的情况)

当 s[0] 与 s[1] 结合后的数在 [10, 26] 之间时,说明前两个字符又能组成联合编码,由于 dp[-1] 不存在,所以制定赋值此时 dp[1] += 1

  1. 填表顺序

从左往右

  1. 返回值

应该返回 dp[n - 1] 的值,因为遍历完字符串中最后一位数字才算结束,表示在 [0, n-1] 区间上总共的解码方法

示例代码:

int numDecodings(string s) {// dp[i]表示第i位置解码方法总数// 解码方式分两种:1.第i位单独解码  2.第i-1位与第i位组合解码int n = s.size();vector<int>dp(n, 0);// 1.初始化dp[0] = s[0] == '0' ? 0 : 1;if (n == 1) { return dp[0]; }  // 防止后续越界if (s[1] <= '9' && s[1] >= '1') { dp[1] += dp[0]; }     // i=0、1分别解码算一种方法int val = (s[0] - '0') * 10 + (s[1] - '0');if (val <= 26 && val >= 10) { dp[1] += 1; }for (int i = 2; i < n; i++){// 单独解码if (s[i] <= '9' && s[i] >= '1') { dp[i] += dp[i - 1]; }// 与前一位联合解码val = (s[i - 1] - '0') * 10 + (s[i] - '0');if (val <= 26 && val >= 10){dp[i] += dp[i - 2];}}return dp[n - 1];
}

总结

​ 对于诸如此类的线性dp数组的题型,最大的共性就是“以 i 位置开始/结束,怎么样怎么样”。我们首先要做的就是确定 dp[i] 表示的是什么,状态转移方程是如何确定dp数组元素之间的推导关系的,接着初始化控制赋值流程,处理边界条件防止越界访问,确定填表顺序和返回值,最后根据思路编写代码。
在这里插入图片描述

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

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

相关文章

Excel:使用VLOOKUP函数,抓取指定数据,后一个列

Excel:使用VLOOKUP函数&#xff0c;抓取指定数据&#xff0c;后一个列 我们有这样一个数据源 要是实现这个页面的赋值 就是对应关系映射 使用 VLOOKUP(A2,Sheet2!$A$2:$B$9,2,FALSE)第一个参数是需要匹配的单元格。 第二个参数是数据源&#xff0c;我这里数据源用的是shee…

Unity 打开外部程序的方法

在Unity中打开外部程序可以使用System.Diagnostics.Process类。 1、编写控制脚本&#xff1a; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using UnityEngine;public class OpenExternalProgram : MonoBehaviour {public strin…

spring boot actuator 安全配置 springboot的安全性

关于springboot Actuator框架的安全配置方案&#xff1a; 加入security安全验证框架 方案一&#xff1a; 配置信息&#xff1a; spring:security:user:password: adminname: adminmanagement:endpoints:web:base-path: /monitorexposure:include: "*"# 排除端点e…

大语言模型中的强化学习与迁移学习技术

文章目录 大语言模型中的强化学习与迁移学习技术大语言模型常用的训练方法主要包括以下几种强化学习在大语言模型中的作用与意义迁移学习在大语言模型中的作用与意义异同强化学习在大语言模型中的具体技术:迁移学习在大语言模型中的具体技术:Agent与Agent框架基于大语言模型预…

Unity图集编辑器

图集编辑器 欢迎使用图集编辑器新的改变编辑器图片 欢迎使用图集编辑器 Unity图集操作很是费劲 无法批量删除和添加图集中的图片 新的改变 自己写了一个图集编辑器 客&#xff1a; 支持批量删除 左键点击图片代表选中 右键点击图标定位到资产支持批量添加 选中图片拖拽到编…

python统计分析——单样本均值检验

参考资料&#xff1a;python统计分析【托马斯】 1、单样本均值的t检验 检验一个正态分布数据的均值和一个参考值的差异&#xff0c;我们一般使用单样本t检验&#xff0c;该检验基于t分布。 如果我们知道一个正态分布总体的均值和标准差&#xff0c;那么我们可以计算对应的标准…

CMOS逻辑门电路

按照制造门电路的三极管不同&#xff0c;分为MOS型、双极性和混合型。MOS型集成逻辑门有CMOS、NMOS、PMOS&#xff1b;双极型逻辑门有TTL&#xff1b;混合型有BiCMOS。 CMOS门电路是目前使用最为广泛、占主导地位的集成电路。早期CMOS电路速度慢、功耗低&#xff0c;后来随着制…

Rust教程:How to Rust-基本类型

专栏简介 本专栏是优质Rust技术专栏&#xff0c;推荐精通一门技术栈的蟹友&#xff0c;不建议完全无计算机基础的同学 感谢Rust圣经开源社区的同学&#xff0c;为后来者提供了非常优秀的Rust学习资源 本文使用&#xff1a; 操作系统macOS Sonoma 14 / Apple M1编译器&#…

ubuntu编译OpenCV and seetaFace2

opencv opencv-4.5.2 opencv_contrib-4.5.2 SeetaFace2 SeetaFace2-master https://github.com/seetafaceengine 指定安装目录&#xff0c;和OpenCV放一个目录下了 安装前 安装 安装后 Qt安装 Windows下 Linux下 报错1 原因&#xff1a; 报错…

Opencv 读取灰度图像会识别为3通道问题

场景&#xff1a; 我们都知道灰度图或者红外图都是单通道图片&#xff0c;而彩色图片是三通道图片。但是当我们用img.shape读取灰度图/红外图片的时候返回的却是三通道结果。 import cv2img_path r灰度图 img cv2.imread(img_path) print(img.shape) # 如果我将图片灰度处理…

HTTP状态 405 - 方法不允许

方法有问题。 用Post发的请求&#xff0c;然后用Put接收的。 大家也可以看看是不是有这种问题 <body><h1>HTTP状态 405 - 方法不允许</h1><hr class"line" /><p><b>类型</b> 状态报告</p><p><b>消息…

Rust控制台输出跑马灯效果,实现刷新不换行,实现loading效果

要在 Rust 中实现控制台刷新而不换行&#xff0c;以实现类似 "loading" 状态的效果&#xff0c;你可以使用 \r&#xff08;回车符&#xff09;来覆盖上一行的内容。 use std::io::{self, Write}; use std::thread; use std::time::Duration;fn main() {let loading_…

没学数模电可以玩单片机吗?

我们首先来看一下数电模电在单片机中的应用。数电知识在单片机中主要解决各种数字信号的处理、运算&#xff0c;如数制转换、数据运算等。模电知识在单片机中主要解决各种模拟信号的处理问题&#xff0c;如采集光照强度、声音的分贝、温度等模拟信号。而数电、模电的相互转换就…

MongoDB 7.x 绑定多个IP(bindIp)和IP范围段(IP/24)

早上安装了最新版的MOngoDB7.0&#xff0c;仅仅是想测试一些功能&#xff0c;暂无复杂操作的想法。 于是在远程的机器上&#xff0c;安装启动&#xff0c;一切正常。 网上找了教程&#xff0c;绑定IP的做法基本是修改mongod.cfg文件中的bindIp属性&#xff1a; Windows系统的…

蓝桥杯_day6

文章目录 不同路径不同路径II拿金币珠宝的最高价值 不同路径 【题目描述】 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为…

十一、Spring源码学习之registerListeners方法

registerListeners()方法 protected void registerListeners() {// Register statically specified listeners first.//获取容器中事件监听并存放到多播器中 applicationListenersfor (ApplicationListener<?> listener : getApplicationListeners()) {getApplicationE…

Flutter——用户关闭键盘后强制拉起键盘

Bug背景 今天在弄输入框的时候&#xff0c;发现用户手动关闭键盘后&#xff0c;因为自定义组件的特殊性&#xff0c;我在点击输入框后并没有唤起键盘。 一般点击输入框或者某个组件&#xff1a; GestureDetector(onTap: () {FocusScope.of(context).requestFocus(_focusNode…

解决element ui中的el-tree组件default-checked-keys默认勾选节点问题

解决element ui中的el-tree组件default-checked-keys默认勾选节点问题 需求解决方法方法1方法2 需求 选中子节点的时候&#xff0c;父节点必须被选中&#xff0c;但是仅展示被选中父节点和子节点 解决方法 方法1 html部分代码&#xff1a; <el-treeclass"filter-tr…

【I.MX6ULL移植】Ubuntu-base根文件系统移植

1.下载Ubuntu16.04根文件系统 http://cdimage.ubuntu.com/ 1 2 3 4 5 2.解压ubuntu base 根文件系统 为了存放 ubuntu base 根文件系统&#xff0c;先在 PC 的 Ubuntu 系统中的 nfs 目录下创建一个名为 ubuntu_rootfs 的目录&#xff0c;命令如下&#xff1a; 【注意&…

基于单片机病房呼叫系统数码管显示房号设计

**单片机设计介绍&#xff0c;基于单片机病房呼叫系统数码管显示房号设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机病房呼叫系统数码管显示房号设计概要主要涵盖了利用单片机技术实现病房呼叫系统&#xff0c;并…