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

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

文章目录

  • 【动态规划】斐波那契数列模型
    • 前言
    • 一、第 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.mfbz.cn/a/497766.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【图像合成】基于DCGAN典型网络的MNIST字符生成(pytorch)

关于 近年来&#xff0c;基于卷积网络&#xff08;CNN&#xff09;的监督学习已经 在计算机视觉应用中得到了广泛的采用。相比之下&#xff0c;无监督 使用 CNN 进行学习受到的关注较少。在这项工作中&#xff0c;我们希望能有所帮助 缩小了 CNN 在监督学习和无监督学习方面的成…

scala-idea环境搭建及使用

环境搭建 创建一个新项目&#xff0c;选择maven工程 点击next&#xff0c;写入项目名&#xff0c;然后finish 注意&#xff1a;默认下&#xff0c;maven不支持scala的开发&#xff0c;需要引入scala框架&#xff0c;右键项目点击-》add framework pport....&#xff0c;在下图…

Unity 实现鼠标左键进行射击

发射脚本实现思路 分析 确定用户交互方式&#xff1a;通过鼠标左键点击发射子弹。确定子弹发射逻辑&#xff1a;每次点击后有一定时间间隔才能再次发射。确定子弹发射源和方向&#xff1a;子弹从枪口&#xff08;Transform&#xff09;位置发射&#xff0c;沿枪口方向前进。 变…

Spring Boot 工程开发常见问题解决方案,日常开发全覆盖

本文是 SpringBoot 开发的干货集中营&#xff0c;涵盖了日常开发中遇到的诸多问题&#xff0c;通篇着重讲解如何快速解决问题&#xff0c;部分重点问题会讲解原理&#xff0c;以及为什么要这样做。便于大家快速处理实践中经常遇到的小问题&#xff0c;既方便自己也方便他人&…

移动端开发思考:Uniapp的上位替代选择

文章目录 前言跨平台开发技术需求技术选型uniappFlutterMAUIAvalonia安卓原生 Flutter开发尝试Avalonia开发测试测试项目新建项目代码MainViewMainViewModel 发布/存档 MAUI实战&#xff0c;简单略过打包和Avalonia差不多 总结 前言 作为C# .NET程序员&#xff0c;我有一些移动…

Python图像处理——计算机视觉中常用的图像预处理

概述 在计算机视觉项目中&#xff0c;使用样本时经常会遇到图像样本不统一的问题&#xff0c;比如图像质量&#xff0c;并非所有的图像都具有相同的质量水平。在开始训练模型或运行算法之前&#xff0c;通常需要对图像进行预处理&#xff0c;以确保获得最佳的结果。图像预处理…

MySQL---触发器

一、介绍 触发器是与表有关的数据库对象&#xff0c;指在insert/update/delete之前(BEFORE)或之后(AFTER)&#xff0c;触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性, 日志记录 , 数据校验等操作 。 使用别名OLD和NEW来引用触…

Ubuntu20.04安装OpenCV并在vsCode中配置

1. 安装OpenCV 1.1 安装准备&#xff1a; 1.1.1 安装cmake sudo apt-get install cmake 1.1.2 依赖环境 sudo apt-get install build-essential libgtk2.0-dev libavcodec-dev libavformat-dev libjpeg-dev libswscale-dev libtiff5-dev sudo apt-get install libgtk2.0-d…

go的通信Channel

go的通道channel是用于协程之间数据通信的一种方式 一、channel的结构 go源码&#xff1a;GitHub - golang/go: The Go programming language src/runtime/chan.go type hchan struct {qcount uint // total data in the queue 队列中当前元素计数&#xff0c;…

Day54:WEB攻防-XSS跨站Cookie盗取表单劫持网络钓鱼溯源分析项目平台框架

目录 XSS跨站-攻击利用-凭据盗取 XSS跨站-攻击利用-数据提交 XSS跨站-攻击利用-flash钓鱼 XSS跨站-攻击利用-溯源综合 知识点&#xff1a; 1、XSS跨站-攻击利用-凭据盗取 2、XSS跨站-攻击利用-数据提交 3、XSS跨站-攻击利用-网络钓鱼 4、XSS跨站-攻击利用-溯源综合 漏洞原理…

智慧管道物联网远程监控解决方案

智慧管道物联网远程监控解决方案 智慧管道物联网远程监控解决方案是近年来在智能化城市建设和工业4.0背景下&#xff0c;针对各类管道网络进行高效、安全、精准管理的前沿科技应用。它融合了物联网技术、大数据分析、云计算以及人工智能等多种先进技术手段&#xff0c;实现对管…

玫瑰图和雷达图(自备)

目录 玫瑰图 数据格式 绘图基础 绘图升级&#xff08;文本调整&#xff09; 玫瑰图 下载数据data/2020/2020-11-24 mirrors_rfordatascience/tidytuesday - 码云 - 开源中国 (gitee.com) R语言绘图—南丁格尔玫瑰图 - 知乎 (zhihu.com) 数据格式 rm(list ls()) libr…

Unity | 工具类-UV滚动

一、内置渲染管线Shader Shader"Custom/ImageRoll" {Properties {_MainTex ("Main Tex", 2D) "white" {}_Width ("Width", float) 0.5_Distance ("Distance", float) 0}SubShader {Tags {"Queue""Trans…

AugmentedReality之路-显示隐藏AR坐标原点(3)

本文介绍如何显示/隐藏坐标原点&#xff0c;分析AR坐标原点跟手机的位置关系 1、AR坐标原点在哪里 当我们通过AugmentedReality的StartARSession函数打开AR相机的那一刻&#xff0c;相机所在的位置就是坐标原点。 2、创建指示箭头资产 1.在Content/Arrow目录创建1个Actor类…

腾讯云4核8G服务器多少钱?12M带宽646元15个月,买1年送3月

2024年腾讯云4核8G服务器租用优惠价格&#xff1a;轻量应用服务器4核8G12M带宽646元15个月&#xff0c;CVM云服务器S5实例优惠价格1437.24元买一年送3个月&#xff0c;腾讯云4核8G服务器活动页面 txybk.com/go/txy 活动链接打开如下图&#xff1a; 腾讯云4核8G服务器优惠价格 轻…

记录minio、okhttp、kotlin一连环的版本冲突问题

问题背景 项目中需要引入minio&#xff0c;添加了如下依赖 <dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.5.2</version></dependency> 结果运行报错&#xff1a; Caused by: java.la…

黑群晖基于docker配置frp内网穿透

前言 我的黑群晖需要设置一下内网穿透来外地访问&#xff0c;虽然zerotier的p2p组网已经很不错了&#xff0c;但是这个毕竟有一定的局限性&#xff0c;比如我是ios的国区id就下载不了zerotier的app&#xff0c;组网不了 1.下载镜像 选择第一个镜像 2.映射文件 配置frpc.ini&a…

基于Spring Boot 3 + Spring Security6 + JWT + Redis实现登录、token身份认证

基于Spring Boot3实现Spring Security6 JWT Redis实现登录、token身份认证。 用户从数据库中获取。使用RESTFul风格的APi进行登录。使用JWT生成token。使用Redis进行登录过期判断。所有的工具类和数据结构在源码中都有。 系列文章指路&#x1f449; 系列文章-基于Vue3创建前端…

【机器学习300问】55、介绍推荐系统中的矩阵分解算法是什么、有什么用、怎么用?

本来这篇文章我想先讲矩阵分解算法是什么东西的&#xff0c;但这样会陷入枯燥的定义中去&#xff0c;让原本非常有趣技术在业务场景中直观的使用路径被切断。所以我觉得先通过一个具体的推荐算法的例子&#xff0c;来为大家感性的介绍矩阵分解有什么用会更加合理。 如果你还不知…

iOS开发进阶(十一):ViewController 控制器详解

文章目录 一、前言二、UIViewController三、UINavigationController四、UITabBarController五、UIPageViewController六、拓展阅读 一、前言 iOS 界面开发最重要的首属ViewController和View&#xff0c;ViewController是View的控制器&#xff0c;也就是一般的页面&#xff0c;…
最新文章