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

 Welcome to 9ilk's Code World

       

(๑•́ ₃ •̀๑) 个人主页:       9ilk

(๑•́ ₃ •̀๑) 文章专栏:     算法Journey 


🏠 第N个泰波那契数模型

📌 题目解析

第N个泰波那契数

  • 题目要求的是泰波那契数,并非斐波那契数。

📌 算法原理

🎵 递归

函数设置:我们可以设置一个Taibo()函数,它能帮我们求出第n个泰波那契数。

函数返回值:题目保证answer <= 2^31 - 1,设置为int即可。

函数实现:根据定义求泰波那契数,我们需要前三个的泰波那契数相加,也就是Taibo(n-3) + Taibo(n-2) + Taibo(n-1)。

函数边界条件:对于n = 0,1,2是边界情况,我们可以提前处理。

参考代码:

class Solution {
public:long Taibo(int n){if(n == 0)return 0;if(n == 1 || n == 2)return 1;return Taibo(n-1) + Taibo(n-2) + Taibo(n-3);}int tribonacci(int n){return Taibo(n);}
};

🎵 记忆化搜索

对于解法一存在下面的问题:

我们发现在递归过程有的节点的值(比如上图的Taibo(3))在第3层就已经求得了,但是其他节点递归深入时又重新计算了,导致了不必要的时间和栈空间的开销(时间复杂度:O(3^n),空间复杂度:O(N))。本题虽然n最大为37,但其实栈空间和时间开销已经很大了,肯定会超时,我们可以采取记忆化搜索的方法:

1. 添加一个备忘录。

2. 每次递归返回时,将结果放到备忘录里面。

3. 在每次进入递归时,往备忘录里查询是否已经记录。

参考代码:

class Solution {
public:vector<int> memory;long Taibo(int n){if (memory[n] != -1) //查看备忘录return memory[n];long ret = Taibo(n - 1) + Taibo(n - 2) + Taibo(n - 3);;memory[n] = ret; //存进备忘录return ret;}int tribonacci(int n){memory.resize(38, -1); //创建一个备忘录memory[0] = 0;memory[1] = memory[2] = 1;return Taibo(n);}
};
  • 此时比之前大大避免了不必要的时间开销,时间复杂度是(n)。

🎵 动态规划

我们动态规划分为以下几步:

1. 状态表示:

  • 所谓状态表示其实是dp表里每个值代表的含义。

Q:状态从何而来?

  • 题目要求(比如本题已经告诉我们要求的是第n个泰波那契数)。
  • 经验 + 题目要求(后面我们再提)。
  • 分析问题的过程,发现重复的子问题。

因此,本题dp[i]的含义是第i个泰波那契数。

2. 状态转移方程:

状态转移方程回答的是dp[i]怎么得到的问题,一般我们从"最近一步"得到。

比如本题中,由定义知,在 n >= 0 的条件下 Tn+3 = Tn + Tn+1 + Tn+2,不就是dp[i] = dp[i-1] + dp[i-2] + dp[i-3]吗?

3. 初始化:

初始化保证我们填表的时候不发生越界!

当遇到n=2时,此时n-3=-1很明显会会发生越界,因此对于边界情况,我们可以提前确定好边界位置在dp表中的值,即dp[0] = 0, dp[1] = dp[2] = 1。

4. 填表顺序:

动态规划需要状态的推导,只有确定好填表顺序,才能确保在填写当前状态时,所需要的状态已经计算过了。

本题很明显是从左往右填表

5. 返回值:

我们需要根据题目要求+状态表示来确定返回值。

注:dp[i]不一定就是我们所需的返回值,我们还需结合题目要求,本题dp[n]就是我们需要的返回值。

参考代码:

class Solution 
{
public:int tribonacci(int n){//1.状态表示:dp[i]表示第N个泰波那切数vector<int> dp(38,-1);//2.初始化dp[0] = 0;dp[1] = dp[2] = 1;//3.填表for(int i = 3 ; i <= n ; i ++){dp[i] = dp[i-1] + dp[i-2] + dp[i-3];//4.状态转移方程} return dp[n];//5.返回值}
};

时间复杂度:O(N)

空间复杂度:O(1)

🎵 滚动数组

我们用vector容器来模拟dp表,但其实可以进一步优化,当求dp[i]只有前面的若干个状态,最前面的几个状态不需要浪费空间,此时可以使用滚动数组优化

我们可以利用四个变量来储存最近一次求泰波那契数的四个状态,不断进行滚动,最终求得目标结果!

注:对于赋值顺序,不能从右往左赋,即c = d,b = c, a = b因为d给c之后,c被d覆盖了,但是b想要的是c的,而c原本的值被覆盖了!

参考代码:

class Solution 
{
public:int tribonacci(int n){//1.状态表示:dp[i]表示第N个泰波那切数if (n == 0) return 0;if (n == 1 || n == 2) return 1;int a = 0;int b = 1;int c = 1;int d = 0;//3.填表for (int i = 3; i <= n; i++){d = a + b + c;;//状态转移方程a = b;b = c;c = d;}return d;}
};

🏠 三步问题

📌 题目解析

三步问题

📌 算法原理

1. 状态表示(经验 + 题目要求):

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

2. 状态转移方程:

我们从i位置的状态,最近的一步来划分问题,由于小孩一次可以上1阶,2阶,3阶:

  • 从i-1位置上来,此时dp[i] += dp[i-1]。
  • 从i-2位置上来,此时dp[i] += dp[i-2]。
  • 从i-3位置上来,此时dp[i] += dp[i-3]。

因此状态转移方程:dp[i] = dp[i-1] + dp[i-2] + dp[i-3]

3. 初始化:

 对于第1,2,3级的台阶,取它们的最近状态可能会造成数组越界(比如i为2时,i-3得-1会越界),因此我们可以提前设置好它们的状态:dp[1] = 1 , dp[2] = 2,dp[3] = 4

4. 填表顺序:

由状态转移方程知,我们i位置的状态依赖于前几个位置的状态,因此我们填表顺序是从左往右填。

5.返回值:

我们要求的是上到第n阶楼梯的总方法,直接返回dp[n]即可,注意要对结果模1000000007

参考代码:

class Solution 
{
public:int waysToStep(int n){if(n == 1 || n == 2) return n;if(n == 3) return 4;long a = 1; //dp[1]long b = 2; //dp[2] long c = 4; //dp[3]long d = 0; for(int i = 4 ; i <= n ; i ++) //空间优化{d = (a + b + c)%1000000007; //状态转移方程a = b;b = c;c = d;}      return d;}
};

🏠 最小花费爬楼梯

📌 题目解析

最小花费爬楼梯

  • 假设n为数组元素个数,则本题中楼梯顶部指的是dp[n],并非dp[n-1]。

📌 算法原理

🎵 解法一 (以i位置为结尾)

1. 状态表示:

  • dp[i]表示:到达 i 位置时,所需支付的最少费用。

2. 状态转移方程:

用i位置的最近一步(之前或之后的状态),推导出dp[i]的值。

  • 当到达i-1位置时,支付cost[i-1],走一步到达i位置 -> dp[i-1] + cost[i-1]。
  • 当到达i-2位置时,支付cost[i-2],走两步到达i位置 -> dp[i-2] + cost[i-2]。
  • 我们要么选择从i-1位置到i,要么选择从i-2位置到i,我们要的是最小花费,则选最小的即可。

因此状态转移方程:dp[i] = min(dp[i-1]+cost[i-1] , dp[i-2] + cost[i-2])。

3.初始化

我们需要保证填表的时候不越界,本题可以选择从下标为0或下标为1的位置开始爬楼梯,因此这两个位置最初的花费是0,即dp[0] = dp[1] = 0。

4. 填表顺序

根据我们的状态转移方程,我们需i位置之前的状态,因此填表顺序是从左往右填。

5. 返回值

返回达到楼梯顶部的最低花费,返回dp[n]即可。

参考代码:

class Solution {
public:int minCostClimbingStairs(vector<int>& cost) {int n = cost.size();if(n == 1 || n == 0) return  0;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]; //返回值}
};

🎵 解法二 (以i位置为起点)
 

1. 状态表示:

  • dp[i]表示:从i位置出发,所需支付的最少费用。

2. 状态转移方程:

用i位置的最近一步(之前或之后的状态),推导出dp[i]的值。

  • 支付cost[i], 往后走一步,从i+1的位置出发到终点 -> dp[i+1] + cost[i]
  • 支付cost[i], 往后走两步,从i+2的位置出发到终点 -> dp[i+2] + cost[i]
  • 我们从i位置要么选择走一步到终点,要么选择走两步到终点,我们要的是最小花费,则选最小的即可。

因此状态转移方程:dp[i] = min(dp[i+1] + cost[i] , dp[i+2] + cost[i])。

3.初始化

对于n-1位置和n-2位置作为出发点,此时他们走一步或两步就到顶部了,因此i+1和i+2会使他们越界,我们只需支付他们对应的cost即可,即dp[n-1] = cost[n-1] && dp[n-2] = cost[n-2]

4. 填表顺序

根据我们的状态转移方程,我们需i位置之后的状态,因此填表顺序是从右往左填。

5. 返回值

我们是从0或1位置为起点出发的,我们返回两者最小即可,即min(dp[0],dp[1])。

参考代码:

class Solution {
public:int minCostClimbingStairs(vector<int>& cost) {int n = cost.size();vector<int> dp(n);dp[n-2] = cost[n-2] ;dp[n-1] = cost[n-1];for(int i = n-3 ; i >= 0 ; i--){dp[i] = min(dp[i+1]+cost[i],dp[i+2]+cost[i]);} return min(dp[1],dp[0]);}
};

🏠 解码方法

📌 题目解析

91. 解码方法 - 力扣(LeetCode)

  • 本题可能存在无法解码的字符串。
  • 字符串中可能包含前导0。

📌 算法原理

1.状态表示:

根据经验+题目要求,我们可以设置dp[i]的状态:字符串以i位置结尾时,解码方法的总数

2. 状态转移方程:

我们还是按照最近一步来划分问题,对于i位置的解码我们以下两种情况:

(1) s[i] 单独解码:

  • 解码成功('1' <= s[i] <= '9',‘0’无法参与解码),此时解码总方法等于前一个位置的解码方法总数,即dp[i-1]。 
  • 解码失败,此时为0

(2) s[i] 与 s[i-1]解码

  • 解码成功(0 <= b*10+a <= 26),时解码总方法等于第i-2个位结尾字符串的解码方法总数,即dp[i-2]。 
  • 解码失败,此时为0

因此状态转移方程为:dp[i] = dp[i-1] + dp[i-2]

3. 初始化:

(1) i = 0时,位于字符串的第一个字符,我们只需判断它单独解码情况是否成立,取值可能为0,1。

(2) i = 1时,位于字符串的第二个字符,首先要单独解码就得先判断第一个字符能否单独解码否则没意义,能单独解码则dp[1]++;再判断与s[0]是否能解码,能则dp[1]++。其可能取值为0,1,2。

4. 状态转移方程 :

根据状态转移方程,我们需要之前位置的状态,因此填表顺序是从左往右。

5. 返回值:

由题意得,最终需要的是以size-1为位置结尾的字符串的所有解码方法,因此返回dp[size-1]。

参考代码:

class Solution {
public:int numDecodings(string s){int n = s.size();vector<int> dp(n);dp[0] = s[0] != '0';//初始化处理边界if(n == 1) return dp[0];if(s[0] != '0' && s[1] != '0') dp[1] += 1;//s[1]单独解码int t = (s[0]-'0')*10 + s[1] - '0'; if(t >= 10 && t <= 26) dp[1] += 1 ;//s[1]与前一个位置解码for(int i = 2 ; i < n ; i ++){//一个数编码if(s[i] != '0') dp[i] += dp[i-1];//两个数编码int t = (s[i-1]-'0')*10 + s[i] - '0'; if(t >= 10 && t <= 26) dp[i] += 1;}return dp[n-1];}
};

🎵 优化(虚拟节点)

Q:我们发现这两段代码相似度较高,处理逻辑是一样的,能不能把边界情况放进循环里处理呢?

这里我们介绍一下虚拟节点

我们可以在原dp表基础上扩充一个位置,保证最后一个位置下标为n,这样在处理字符串中原来下标为0位置的字符时,它在新dp表的下标变为1,这样i-1就不会越界!但是同时要注意两个问题:

1. 虚拟节点里面的值,要保证后面的填表时正确的。(比如对于新dp表的0下标位置,我们要保证对于如果字符串第二个位置的字符能跟第一个字符解码,此时需要新dp表i-2位置的值,也就是dp[0],此时我们需要设置它为1,表示存在第二个字符和第二个字符共同解码这一种解码方法)

2. 下标的映射关系:我们新dp表下标在原来基础上+1,但是s[i]的size并没有变化!

class Solution
{
public:int numDecodings(string s)
{
//优化int n=s.size();vector<int>dp(n + 1);dp[0]=1;//保证后面的填表是正确的dp[1]= s[1 - 1] != '0';
注意映射关系s[1-1]下标映射关系for(inti=2;i<=n;i++){if(s[i-1]!='0')dp[i]+=dp[i-1];//处理单独编码的情况int =(s[i-2]-'0')*10+s[i-1]-'0';//第二种情况所对应的数if(t>=10 &&t<=26)dp[i]+=dp[i] += dp[i - 2];}return dp[n];
}

完。

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

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

相关文章

单片机-STM32 WIFI模块--ESP8266 (十二)

1.WIFI模块--ESP8266 名字由来&#xff1a; Wi-Fi这个术语被人们普遍误以为是指无线保真&#xff08;Wireless Fidelity&#xff09;&#xff0c;并且即便是Wi-Fi联盟本身也经常在新闻稿和文件中使用“Wireless Fidelity”这个词&#xff0c;Wi-Fi还出现在ITAA的一个论文中。…

计算机的错误计算(二百二十二)

摘要 利用大模型化简计算 实验表明&#xff0c;虽然结果正确&#xff0c;但是&#xff0c;大模型既绕了弯路&#xff0c;又有数值计算错误。 与前面相同&#xff0c;再利用同一个算式看看另外一个大模型的化简与计算能力。 例1. 化简计算摘要中算式。 下面是与一个大模型的…

ansible自动化运维实战--软件包管理模块、服务模块、文件模块和收集模块setup(4)

文章目录 一、软件包管理模块1.1、功能1.2、常用参数1.3、示例 二、服务模块2.1、功能2.2、服务模块常用参数2.3、示例 三、文件与目录模块3.1、file功能3.2、常用参数3.3、示例 四、收集模块-setup4.1、setup功能4.2、示例 一、软件包管理模块 1.1、功能 Ansible 提供了多种…

高速光模块中的并行光学和WDM波分光学技术

随着AI大模型训练和推理对计算能力的需求呈指数级增长&#xff0c;AI数据中心的网络带宽需求大幅提升&#xff0c;推动了高速光模块的发展。光模块作为数据中心和高性能计算系统中的关键器件&#xff0c;主要用于提供高速和大容量的数据传输服务。 光模块提升带宽的方法有两种…

Linux命令行配置网络代理

在Linux命令行中&#xff0c;你可以使用以下方法设置网络代理服务器。 本文演示代理地址为&#xff1a;http://192.168.1.30:7890 请根据实际代理地址进行替换 临时代理 使用环境变量的方法&#xff1a; 打开终端&#xff0c;并输入以下命令&#xff1a; export http_proxyhtt…

SpringBoot3+Vue3开发学生选课管理系统

功能介绍 分三个角色登录&#xff1a;学生登录&#xff0c;老师登录&#xff0c;教务管理员登录&#xff0c;不同用户功能不同&#xff01; 1.学生用户功能 选课记录&#xff0c;查看选课记录&#xff0c;退选。选课管理&#xff0c;进行选课。通知管理&#xff0c;查看通知消…

牛客周赛 Round 78 A-C

A.时间表查询&#xff01; 链接&#xff1a;https://ac.nowcoder.com/acm/contest/100671/A 来源&#xff1a;牛客网 题目描述 今天是2025年1月25日&#xff0c;今年的六场牛客寒假算法基础集训营中&#xff0c;前两场比赛已经依次于 20250121、20250123 举行&#xff1b;而…

Android - 通过Logcat Manager简单获取Android手机的Log

由于工作需要&#xff0c;经常需要获取Android手机的Log。 平常都是通过adb命令来获取&#xff0c;每次都要写命令。 偶然的一个机会&#xff0c;我从外网发现了一个工具 Logcat Manager&#xff0c;只需要通过简单的双击即可获取Android的Log&#xff0c;这里也分享一下。 目…

苍穹外卖-day10

苍穹外卖-day10 Spring Task Spring Task 是Spring框架提供的任务调度工具&#xff0c;可以按照约定的时间自动执行某个代码逻辑。 cron表达式 cron表达式其实就是一个字符串&#xff0c;通过cron表达式可以定义任务触发的时间 **构成规则&#xff1a;**分为6或7个域&…

一文详解Filter类源码和应用

背景 在日常开发中&#xff0c;经常会有需要统一对请求做一些处理&#xff0c;常见的比如记录日志、权限安全控制、响应处理等。此时&#xff0c;ServletApi中的Filter类&#xff0c;就可以很方便的实现上述效果。 Filter类 是一个接口&#xff0c;属于 Java Servlet API 的一部…

(2)STM32 USB设备开发-USB虚拟串口

例程&#xff1a;STM32USBdevice: 基于STM32的USB设备例子程序 - Gitee.com 本篇为USB虚拟串口教程&#xff0c;没有知识&#xff0c;全是实操&#xff0c;按照步骤就能获得一个STM32的USB虚拟串口。本例子是在野火F103MINI开发板上验证的&#xff0c;如果代码中出现一些外设的…

K8S中的数据存储之基本存储

基本存储类型 EmptyDir 描述&#xff1a;当 Pod 被调度到节点上时&#xff0c;Kubernetes 会为 Pod 创建一个空目录&#xff0c;所有在该 Pod 中的容器都可以访问这个目录。特点&#xff1a; 生命周期与 Pod 绑定&#xff0c;Pod 删除时&#xff0c;数据也会丢失。适用于临时…

谈谈RTMP|RTSP播放器视频view垂直|水平反转和旋转设计

技术背景 我们在做RTMP|RTSP播放器的时候&#xff0c;有这样的技术诉求&#xff0c;有的摄像头出来的数据是有角度偏差的&#xff0c;比如“装倒了”&#xff0c;或者&#xff0c;图像存在上下或者左右反转&#xff0c;这时候&#xff0c;就需要播放器能做响应的处理&#xff…

自然语言处理——从原理、经典模型到应用

1. 概述 自然语言处理&#xff08;Natural Language Processing&#xff0c;NLP&#xff09;是一门借助计算机技术研究人类语言的科学&#xff0c;是人工智能领域的一个分支&#xff0c;旨在让计算机理解、生成和处理人类语言。其核心任务是将非结构化的自然语言转换为机器可以…

【MFC】C++所有控件随窗口大小全自动等比例缩放源码(控件内字体、列宽等未调整) 20250124

MFC界面全自动等比例缩放 1.在初始化里 枚举每个控件记录所有控件rect 2.在OnSize里&#xff0c;根据当前窗口和之前保存的窗口的宽高求比例x、y 3.枚举每个控件&#xff0c;根据比例x、y调整控件上下左右,并移动到新rect struct ControlInfo {CWnd* pControl;CRect original…

SkyWalking介绍

一款开源的系统性能监控工具(APM) 背景 在解决提报的IT性能问题时&#xff0c;由于缺乏系统性能监控运维的工具&#xff0c;导致问题排查非常困难&#xff0c;尤其是偶发的问题&#xff0c;无法进行问题复现还原&#xff0c;需要一套能实时监控线上系统性能的工具平台。 SkyWal…

Pyecharts之图表组合与布局优化

在数据可视化中&#xff0c;我们经常需要将多个图表组合在一起&#xff0c;以展示不同维度的数据或者进行对比分析。同时&#xff0c;合理的布局能够提升图表的可读性和用户体验。Pyecharts 提供了强大的组件和方法&#xff0c;让我们可以轻松实现图表的组合和布局优化。本篇将…

物业管理平台系统提升社区智能化服务效率与管理水平

内容概要 在现代社会中&#xff0c;物业管理平台系统的出现&#xff0c;为社区的智能化服务带来了革命性的变化。这种系统不仅仅是提升了工作效率&#xff0c;更是通过一系列智能化功能&#xff0c;根本性改变了物业管理的方式。比如&#xff0c;在广告位管理方面&#xff0c;…

Kafka 深入服务端 — 时间轮

Kafka中存在大量的延迟操作&#xff0c;比如延时生产、延时拉取和延时删除等。Kafka基于时间轮概念自定义实现了一个用于延时功能的定时器&#xff0c;来完成这些延迟操作。 1 时间轮 Kafka没有使用基于JDK自带的Timer或DelayQueue来实现延迟功能&#xff0c;因为它们的插入和…

Baklib如何推动企业知识管理的创新与转型探讨

内容概要 在当今快速发展的数字化时代&#xff0c;企业需要不断适应变化&#xff0c;以保持竞争优势。Baklib作为一款企业知识管理中台&#xff0c;扮演着推动数字化转型的重要角色。它通过提供一个集成的知识管理平台&#xff0c;帮助企业高效管理和共享内部及外部的知识资源…