动态规划在斐波那契数列中的应用与优化

动态规划在斐波那契数列中的应用与优化
在这里插入图片描述

文章目录


前言

斐波那契数列是数学领域中一个经典的问题,在计算机科学中也有广泛的应用。从简单的递归算法到优化的动态规划方法,斐波那契数列的求解体现了算法设计和性能优化的精髓。本文将以动态规划为核心,系统地探讨如何高效地计算斐波那契数列,分析不同方法的时间与空间复杂度,并展示动态规划的强大之处。希望通过本研究,为算法设计爱好者提供启发,并在实际问题中应用该技术。


🌞一、1137. 第 N 个泰波那契数

题目链接:https://leetcode.cn/problems/n-th-tribonacci-number/description/

🌜1. 题目解析

Tribonacci 数列是一个递归数列,类似于斐波那契数列,但它的递推公式是:

  • 递推公式T(n) = T(n-1) + T(n-2) + T(n-3),对于 n >= 3
  • 初始条件:
    • T(0) = 0
    • T(1) = 1
    • T(2) = 1

需要实现一个函数 tribonacci(int n),返回第 n 个 Tribonacci 数。

🌜2. 讲解算法原理

状态表示

dp[i] 表示第 i 个 Tribonacci 数,即前 i 个数的第三阶斐波那契数列。换句话说,dp[i] 是定义如下的递推关系:

  • dp[i] = dp[i-1] + dp[i-2] + dp[i-3],对于 i >= 3
状态转移方程

根据题目描述,Tribonacci 数列满足:

  • dp[0] = 0
  • dp[1] = 1
  • dp[2] = 1
  • dp[i] = dp[i-1] + dp[i-2] + dp[i-3],对于 i >= 3
初始化

初始化初始条件,保证在填写表格的时候不会越界:

  • dp[0] = 0
  • dp[1] = 1
  • dp[2] = 1
填表顺序

i = 3 开始递推填表,因为计算 dp[i] 时,需要依赖 dp[i-1], dp[i-2], 和 dp[i-3] 的值,这些值在计算当前状态时必须已知。因此:

  • 填写顺序是从 i = 3 开始递增。
返回值

最后返回 dp[n],即第 n 个 Tribonacci 数。

🌜3. 编写代码

classSolution{public:inttribonacci(int n){// 3. 初始化(初始条件)if(n ==0)return0;if(n ==1|| n ==2)return1;// 1. 状态表示:dp[i] 表示第 i 个 Tribonacci 数 vector<int>dp(n +1); dp[0]=0, dp[1]= dp[2]=1;// 4. 填表顺序:从 i = 3 到 nfor(int i =3; i <= n; i++){// 2. 状态转移方程 dp[i]= dp[i-1]+ dp[i-2]+ dp[i-3];}// 5. 返回值:第 n 个 Tribonacci 数return dp[n];}};

🌜4. 空间优化

classSolution{public:inttribonacci(int n){if(n ==0)return0;if(n ==1|| n ==2)return1;int a =0, b =1, c =1, d =0;// 初始状态for(int i =3; i <= n; i++){ d = a + b + c;// 当前状态依赖于前三个状态 a = b;// 滚动更新 b = c; c = d;}return d;// 返回第 n 项};
  1. 状态转移方程
    • T(n) = T(n-1) + T(n-2) + T(n-3)
    • 每次计算当前值 T(n) 只需要 T(n-1)T(n-2)T(n-3)
  2. 滚动更新变量
    • 变量 abc 分别存储 T(n-3)T(n-2)T(n-1)
  3. 减少空间复杂度
    • 原本需要一个大小为 n 的数组来存储所有状态。
    • 滚动数组通过动态更新变量,将空间复杂度从 O(n) 优化为 O(1)

每次计算 d = a + b + c 后,将 a、b、c 滚动更新为下一轮所需的值:

a = b;// T(n-2) 成为 T(n-3) b = c;// T(n-1) 成为 T(n-2) c = d;// T(n) 成为 T(n-1)

🌞二、面试题 08.01. 三步问题

题目链接:https://leetcode.cn/problems/three-steps-problem-lcci/

🌜1. 题目解析

这道题的目标是计算可以通过步数 1、2 和 3 从 0 到达台阶 n 的所有不同方法的总数。
每次可以选择迈 1 步、2 步或 3 步,并且答案需要对 1 0 9 + 7 10^9 + 7 109+7 取模。

🌜2. 讲解算法原理

状态表示

定义一个数组 dp[i],表示到达第 i 个台阶的方法总数。

  • dp[i] 包括从 i−1i−2、或 i−3 的台阶迈一步到达的所有方案数。
状态转移方程

状态转移公式为:

d p [ i ] = ( d p [ i − 1 ] + d p [ i − 2 ] + d p [ i − 3 ] ) % M O D dp[i] = (dp[i-1] + dp[i-2] + dp[i-3]) \% MOD dp[i]=(dp[i−1]+dp[i−2]+dp[i−3])%MOD

这里的取模操作是为了防止结果过大,确保结果保持在 1 0 9 + 7 10^9 + 7 109+7 的范围内。

初始化

根据题目条件:

  • 如果 n = 1,只有 1 种方法,初始化 dp[1] = 1
  • 如果 n = 2,有 2 种方法,初始化 dp[2] = 2
  • 如果 n = 3,有 4 种方法,初始化 dp[3] = 4
填表顺序

从小到大依次计算 dp[4]dp[n],通过累加前 3 项得到结果。

返回值

最终返回 dp[n] 即可。

🌜3. 编写代码

classSolution{public:intwaysToStep(int n){if(n ==1|| n ==2)return n;if(n ==3)return4;constint MOD =1e9+7; 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])% MOD + dp[i -3])% MOD;}return dp[n];}};

🌞三、746. 使用最小花费爬楼梯

题目链接:https://leetcode.cn/problems/min-cost-climbing-stairs/description/

🌜1. 题目解析

这道题的目标是计算从数组开头或第二个元素出发,到达数组末尾所需的最小花费。
每次可以迈 1 步或 2 步,花费由数组 cost 决定,其中每个位置的值代表站在对应台阶上的代价。

🌜2. 讲解算法原理

状态表示

定义一个数组 dp[i]

  • dp[i] 表示到达台阶 i 所需的最小花费。
状态转移方程

我们可以从前一层(i−1)或前两层(i−2)迈步到 i,取最小值作为最优解:

d p [ i ] = min ⁡ ( d p [ i − 1 ] + c o s t [ i − 1 ] , d p [ i − 2 ] + c o s t [ i − 2 ] ) dp[i] = \min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2]) dp[i]=min(dp[i−1]+cost[i−1],dp[i−2]+cost[i−2])

初始化

因为可以从第 0 层或第 1 层开始:

  • dp[0] = 0:从起点出发,不需要额外代价。
  • dp[1] = 0:从起点开始直接跳到第 1 层,也没有代价。
填表顺序

从小到大计算 dp[i],直至 dp[n] ,其中 n 是楼梯台阶数。

返回值

最终返回 dp[n],即为到达顶部所需的最小花费。

🌜3. 编写代码

1. 解法一:自底向上填表

classSolution{public:intminCostClimbingStairs(vector<int>& cost){int n = cost.size(); vector<int>dp(n +1);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. 解法二:从顶层反推

classSolution{public:intminCostClimbingStairs(vector<int>& cost){int n = cost.size(); 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]+ cost[i], dp[i +2]+ cost[i]);}returnmin(dp[0], dp[1]);}};

🌞四、91. 解码方法

题目链接:https://leetcode.cn/problems/decode-ways/description/

🌜1. 题目解析

本题要求解码一个只包含数字的字符串 s,其中每个数字代表字母表中的字母(1 对应 ‘A’,2 对应 ‘B’,…,26 对应 ‘Z’)。
我们的任务是计算出所有可能的解码方案的数量。

🌜2. 讲解算法原理

状态表示

定义一个数组 dp,其中 dp[i] 表示字符串 s 的前 i 个字符能够解码的方式总数。

状态转移方程

两个字符解码:
如果前两个字符组成的数值在 [10, 26] 范围内,那么它们可以合并解码成一个字母。例如,s[i-2]s[i-1] 组合形成一个数,如果这个数在 [10, 26] 内,则可以由 dp[i-2] 转移:

int t =(s[i-2]-'0')*10+(s[i-1]-'0');if(t >=10&& t <=26) dp[i]+= dp[i-2];

单个字符解码:
如果当前位置的字符 s[i-1] 是有效的(即不等于 ‘0’),它可以独立解码为一个字母。
因此,dp[i] 可以由 dp[i-1] 转移而来:

if(s[i-1]!='0') dp[i]+= dp[i-1];
初始化

dp[0] = 1:表示空字符串有 1 种解码方法。

dp[1] = s[0] != '0':如果第一个字符不为 ‘0’,则有 1 种解码方法,否则没有解码方法。

填表顺序

i = 2 开始填表,一直到 i = n,逐步计算每个 dp[i] 的值。

返回值

最终返回 dp[n],即为整个字符串的解码方法数量。

🌜3. 编写代码

classSolution{public:intnumDecodings(string s){int n = s.size(); vector<int>dp(n +1); dp[0]=1;// 保证后面的填表是正确的 dp[1]= s[0]!='0';// 第一个字符不能是 '0'for(int i =2; i <= n; i++){// 如果当前字符不是 '0',可以单独解码if(s[i -1]!='0') dp[i]+= dp[i -1];// 如果当前和前一个字符组成的数字在 10 到 26 之间,也可以解码int t =(s[i -2]-'0')*10+(s[i -1]-'0');if(t >=10&& t <=26) dp[i]+= dp[i -2];}return dp[n];}};

结语

本文通过对斐波那契数列的动态规划求解方法的分析与优化,展现了动态规划在减少冗余计算、提升算法效率上的显著优势。相比于递归方法,动态规划在时间复杂度和空间复杂度上均有更优的表现,同时也更易扩展到其他问题。斐波那契数列作为算法入门的重要实例,其研究不仅有助于理解动态规划的基本原理,更能为解决更复杂的现实问题奠定基础。未来,动态规划仍将在算法设计领域发挥重要作用,我们也期待更多优化和创新的出现。

在这里插入图片描述

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,17的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是17前进的动力!

在这里插入图片描述

Read more

“现在的AI就像1880年的笨重工厂!”微软CSO斯坦福泼冷水:别急着造神

“现在的AI就像1880年的笨重工厂!”微软CSO斯坦福泼冷水:别急着造神

大模型仍未对上商业的齿轮? 编译 | 王启隆 来源 | youtu.be/aWqfH0aSGKI 出品丨AI 科技大本营(ID:rgznai100) 现在的硅谷,空气里都飘着一股“再不上车就晚了”的焦躁感。 最近 OpenClaw 风头正旺,强势登顶 GitHub,终结了 React 神话,许多人更是觉得“AI 自己干活赚钱”的日子就在明天了。 特别是在斯坦福商学院(GSB)这种地方,台下坐着的都是成天琢磨怎么用下一个技术风口搞个独角兽出来的狠人。 微软的首席科学官(CSO)Eric Horvitz 被请到了这个几乎全美最想用 AI 变现的礼堂里。作为从上世纪 80 年代就开始搞 AI 的绝对老炮、也是微软技术底座的“扫地僧”,这位老哥并没有顺着台下的胃口,去吹捧下个月大模型又要颠覆什么行业,而是兜头给大家浇了一盆带点学术味的冷水。 他讲了一个挺有画面感的比喻:大家都在聊

By Ne0inhk
Godot被AI代码“围攻”!维护者崩溃发声:“不知道还能坚持多久”

Godot被AI代码“围攻”!维护者崩溃发声:“不知道还能坚持多久”

整理 | 郑丽媛 出品 | ZEEKLOG(ID:ZEEKLOGnews) 当大模型能在几秒钟内生成一段“看起来像那么回事”的补丁时,开源社区却开始付出另一种代价。 最近,开源游戏引擎 Godot 的核心维护团队公开吐槽:他们正被大量“AI 生成的低质量代码”淹没。那些代码往往结构完整、注释齐全、描述洋洋洒洒,但真正的问题是——提交者可能并不理解自己交上来的内容。 这件事,并不是简单的“有人偷懒用 AI 写代码”。它正在触及开源协作最核心的东西:信任。 一场悄无声息的“AI 洪水” 事情的导火索来自一条 Bluesky 讨论帖。 Godot 主要维护者之一、同时也是 Godot 商业支持公司 W4 Games 联合创始人的 Rémi Verschelde 表示,所谓的“AI slop”

By Ne0inhk
诺奖得主辛顿最新访谈:1 万个 AI 可以瞬间共享同一份“灵魂”,这就是为什么人类注定被超越

诺奖得主辛顿最新访谈:1 万个 AI 可以瞬间共享同一份“灵魂”,这就是为什么人类注定被超越

当宇宙级的“嘴炮”遇到降维打击。 编译 | 王启隆 来源 | youtu.be/l6ZcFa8pybE 出品丨AI 科技大本营(ID:rgznai100) 打开最新一期知名播客 StarTalk 的 YouTube 评论区,最高赞的一条留言是这样写的: “我长这么大,第一次看到尼尔·德葛司·泰森(Neil deGrasse Tyson)在一档节目里几乎全程闭嘴,像个手足无措的小学生一样乖乖听讲。” 作为全美最知名的天体物理学家,泰森平时的画风是充满激情、喋喋不休、用宇宙的宏大来震撼嘉宾。但这一次,坐在他对面的那位满头银发、带着温和英音的英国老人,仅仅用最平淡的语气,就让整个演播室陷入了数次令人窒息的沉默。 这位老人是 Geoffrey Hinton。深度学习三巨头之一,2024 年诺贝尔物理学奖得主,被公认为“AI 教父”。 对经常阅读 Hinton 演讲的我来说,这也是比较新奇的一幕—

By Ne0inhk
48小时“烧光”56万!三人创业团队濒临破产,仅因Gemini API密钥被盗:“AI账单远超我们的银行余额”

48小时“烧光”56万!三人创业团队濒临破产,仅因Gemini API密钥被盗:“AI账单远超我们的银行余额”

整理 | 苏宓 出品 | ZEEKLOG(ID:ZEEKLOGnews) 「仅过了 48 小时,一笔 8.2 万美元的天价费用凭空出现,较这家小型初创公司的正常月费暴涨近 46000%。」 这不是假设的虚幻故事,而是一家墨西哥初创公司正在经历的真实危机。 近日,一位名为 RatonVaquero 的开发者在 Reddit 发帖求助称,由于他的 Gemini API 密钥被盗用,原本每月仅约 180 美元(约 1242 元)的费用,在短短 48 小时内暴涨到 82,314.44 美元(约 56.8 万元)。对于这家只有三名开发者的小型创业团队来说,这笔突如其来的账单,几乎等同于灭顶之灾。 “我现在整个人都处在震惊和恐慌之中。”RatonVaquero

By Ne0inhk