news 2026/9/16 15:01:57

LeetCode-Book 509. 斐波那契数:暴力递归、记忆化与动态规划的状态压缩全解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeetCode-Book 509. 斐波那契数:暴力递归、记忆化与动态规划的状态压缩全解

LeetCode-Book 509. 斐波那契数:暴力递归、记忆化与动态规划的状态压缩全解

【免费下载链接】LeetCode-Book《剑指 Offer》《图解算法数据结构》《Krahets 笔面试精选 88 题》Python, Java, C++ 解题代码项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Book

本篇技术指南以 LeetCode-Book 仓库《Krahets 笔面试精选 88 题》中 509. 斐波那契数 为核心骨架,系统讲解斐波那契数列的三种解法演进路径(暴力搜索 → 记忆化递归 → 动态规划),并深入剖析动态规划的状态压缩原理与三语言实现。读完本文,你将掌握"重叠子问题"的识别方法、动态规划的四个标准步骤(状态定义、转移方程、初始状态、返回值),以及如何把 $O(N)$ 空间的 DP 表压缩为 $O(1)$ 空间的滚动变量,为后续学习爬楼梯、打家劫舍等经典 DP 题目打下基础。

题目背景与数列定义

斐波那契数(Fibonacci Number)是面试中最经典的入门动态规划题,其递推定义极其简洁:

$$ f(n + 1) = f(n) + f(n - 1) $$

即数列第 $n+1$ 项等于前两项之和,配合初始值 $f(0) = 0$、$f(1) = 1$,可逐项递推出完整数列:$0, 1, 1, 2, 3, 5, 8, 13, \dots$。

尽管题目本身简单,但它承载了算法面试中最重要的思想训练:同一个问题,用朴素递归、记忆化递归、动态规划三种思路实现,性能从指数级优化到线性级,空间从 $O(N)$ 压缩到 $O(1)$。这一"降维打击"的过程,正是动态规划思想的核心价值所在。在本仓库中,该题同时收录于 selected_coding_interview 与剑指 Offer 板块(剑指 Offer 10- I. 斐波那契数列),两处解法一脉相承,可对照阅读。

三种解法思路总览

生成第 $n$ 项斐波那契数,由简到繁主要有以下三种做法:

解法核心原理时间复杂度空间复杂度主要缺点
暴力搜索(朴素递归)将 $f(n)$ 拆分为 $f(n-1)$ 与 $f(n-2)$ 两个子问题递归求解,以 $f(0)$、$f(1)$ 为终止条件$O(2^n)$$O(n)$(递归栈深度)大量重复计算,指数级爆炸
记忆化递归递归基础上新增数组缓存已算出的 $f(0) \sim f(n)$,重复子问题直接查表$O(n)$$O(n)$需要额外 $O(N)$ 存储空间
动态规划以 $f(n+1) = f(n) + f(n-1)$ 为转移方程自底向上迭代$O(n)$$O(n)$,可压缩至 $O(1)$无(本题最佳解法)

从计算效率与空间复杂度两个维度综合衡量,动态规划(含状态压缩)是本题的最佳解法,也是面试中最推荐的实现方式。

暴力搜索:理解"重叠子问题"

暴力搜索的原理非常直观:把 $f(n)$ 问题的计算拆分成 $f(n-1)$ 和 $f(n-2)$ 两个子问题的计算,并不断递归,以 $f(0)$ 和 $f(1)$ 为终止条件。对应伪代码为:

fib(n): if n == 0: return 0 if n == 1: return 1 return fib(n-1) + fib(n-2)

缺点:大量重复的递归计算。例如 $f(n)$ 与 $f(n-1)$ 两者向下递归时,需要各自计算一次 $f(n-2)$ 的值;随着递归深度增加,$f(n-3)$、$f(n-4)$ 等子问题会被重复计算的次数呈指数级增长,整体时间复杂度退化到 $O(2^n)$。这种"子问题被重复求解"的现象,正是动态规划中著名的**重叠子问题(Overlapping Subproblems)**概念——它是判断一个问题能否用动态规划求解的两大特征之一。

记忆化递归:用空间换时间

记忆化递归(Memoization)在递归法的基础上,新建一个长度为 $n$ 的数组,用于在递归时存储 $f(0)$ 至 $f(n)$ 的数字值:

memo = [-1] * (n + 1) # 初始化缓存,-1 表示未计算 fib(n): if n == 0: return 0 if n == 1: return 1 if memo[n] != -1: return memo[n] # 重复遇到某数字直接从数组取用 memo[n] = fib(n-1) + fib(n-2) return memo[n]

其原理是:重复遇到某数字则直接从数组取用,避免了重复的递归计算,将时间复杂度从 $O(2^n)$ 降为 $O(n)$。缺点:记忆化存储需要使用 $O(N)$ 的额外空间。这里"以空间换时间"的取舍,是记忆化与后续动态规划的共同思想基础。

动态规划解析:四步法

动态规划把自顶向下的递归改写为自底向上的迭代,其标准四步为:状态定义 → 转移方程 → 初始状态 → 返回值。

  • 状态定义:设 $dp$ 为一维数组,其中 $dp[i]$ 的值代表斐波那契数列第 $i$ 个数字;
  • 转移方程:$dp[i + 1] = dp[i] + dp[i - 1]$,即对应数列定义 $f(n + 1) = f(n) + f(n - 1)$;
  • 初始状态:$dp[0] = 0$,$dp[1] = 1$,即初始化前两个数字;
  • 返回值:$dp[n]$,即斐波那契数列的第 $n$ 个数字。

对应伪代码为:

dp = [0] * (n + 1) dp[0], dp[1] = 0, 1 for i in range(2, n + 1): dp[i] = dp[i-1] + dp[i-2] return dp[n]

这一流程可以无缝迁移到几乎所有一维 DP 题:例如仓库中的 70. 爬楼梯(lc_70_climbing_stairs.py),其状态定义 $dp[i]$ 为爬到第 $i$ 阶的方法数,转移方程 $dp[i] = dp[i-1] + dp[i-2]$ 与本题几乎一致,只是初始状态改为 $dp[0] = dp[1] = 1$。掌握本题,等于拿到了通往一组"斐波那契系" DP 题的钥匙。

状态压缩:把 $O(N)$ 空间降到 $O(1)$

若新建长度为 $n$ 的 $dp$ 列表,则空间复杂度为 $O(N)$。

状态压缩的核心观察:由于 $dp$ 列表第 $i$ 项只与第 $i-1$ 和第 $i-2$ 项有关,前面的所有状态在计算出后续状态后便不再被使用。因此只需要初始化三个整型变量sumab,利用辅助变量sum使 $a, b$ 两数字交替前进即可(具体实现见下文代码)。这一技巧在 DP 领域极为通用,几乎所有"只依赖前两个状态"的递推问题(爬楼梯、青蛙跳台阶、打家劫舍等)都可套用。

  • 迭代过程中a始终代表 $f(i)$,b始终代表 $f(i+1)$;
  • 每轮先计算sum = a + b得到下一项,再依次将a = bb = sum,实现两个变量"滚动前进";
  • 循环结束后返回a,即为 $f(n)$。

节省了 $dp$ 列表空间,因此空间复杂度降至 $O(1)$。这也是 LeetCode-Book 在本题所有语言实现中统一采用的最终版本。

三语言代码实现(与仓库源码一致)

以下三份实现与仓库 selected_coding_interview/codes 目录下的实际源码完全一致,均为状态压缩后的 $O(1)$ 空间版本:

class Solution: def fib(self, n: int) -> int: a, b = 0, 1 for _ in range(n): a, b = b, a + b return a

对应仓库文件:lc_509_fibonacci_number.py。Python 的多元赋值a, b = b, a + b会先计算右侧的(b, a+b)再同时赋值,天然实现了sum辅助变量的作用,因此无需显式声明sum

class Solution { public int fib(int n) { int a = 0, b = 1, sum; for(int i = 0; i < n; i++){ sum = a + b; a = b; b = sum; } return a; } }

对应仓库文件:lc_509_fibonacci_number.java。Java 需要显式引入sum临时变量完成滚动。

class Solution { public: int fib(int n) { int a = 0, b = 1, sum; for(int i = 0; i < n; i++){ sum = a + b; a = b; b = sum; } return a; } };

对应仓库文件:lc_509_fibonacci_number_s1.cpp。C++ 版本还附带了可独立运行的main()驱动代码:int n = 4; ... cout << slt->fib(n) << endl;,可直接编译运行验证输出。

三种语言的循环控制均为恰好执行 $n$ 次迭代:当 $n = 0$ 时循环体不执行,直接返回初始值a = 0,正确覆盖边界情况;当 $n = 1$ 时执行一次a, b = b, a + b后返回a = 1,同样正确。

从源码看仓库的代码组织约定

细读仓库源码可以发现 LeetCode-Book 在代码组织上的一致性约定:

  • 每个题目文件以lc_前缀 + 题号 + 题名命名(Python 为单文件,Java/C++ 为按题号分目录),如本题的 lc_509_fibonacci_number.py;
  • 文件头部统一记录创建时间与作者信息;
  • Python 版本通过from include import *引入仓库自建的 include 工具包(内含TreeNodeListNode等数据结构工具类与print_matrix等打印工具),并预留Test CaseDriver Code区块便于本地运行调试。

大数越界问题与取模变体(延伸阅读)

LeetCode-Book 在剑指 Offer 板块收录了本题的取模变体——剑指 Offer 10- I. 斐波那契数列,要求答案对1000000007取模,可用于观察大数场景下的处理方式。

  • sfo_10i_fibonacci_numbers_s1.py:在每轮迭代内取模,a, b = b, (a + b) % 1000000007,全程保证数值不越界;
  • sfo_10i_fibonacci_numbers_s2.py:先不处理越界、最后统一return a % 1000000007,并注释说明"不考虑大数越界问题"。

对照学习这两份变体,可以直观理解"迭代中取模"与"结果取模"在数值安全性上的差异,这对 C++/Java 这类存在整型溢出的语言尤为重要。

复杂度分析与面试要点

  • 时间复杂度 $O(n)$:计算 $f(n)$ 需循环 $n$ 次,每轮循环内计算操作使用 $O(1)$;
  • 空间复杂度 $O(1)$:几个标志变量使用常数大小的额外空间(未使用递归栈或长度 $n$ 的数组)。

面试加分要点总结:

  1. 先说出朴素递归的 $O(2^n)$ 时间代价,并指出根因是重叠子问题导致的重复计算;
  2. 再给出记忆化(自顶向下)与动态规划(自底向上)两条优化路径,说明二者时间同为 $O(n)$、但动态规划可进一步空间优化;
  3. 最后展示状态压缩:指出 $dp[i]$ 仅依赖 $dp[i-1]$、$dp[i-2]$,故用三个变量滚动即可,空间降至 $O(1)$;
  4. 若题目要求大数取模,补充说明在迭代中同步取模以避免越界。

这一"暴力 → 记忆化 → DP → 状态压缩"的完整演进链条,是面试官考察动态规划基本功最常使用的考题,务必做到能画递归树、能写四步法、能现场手撕状态压缩代码。

【免费下载链接】LeetCode-Book《剑指 Offer》《图解算法数据结构》《Krahets 笔面试精选 88 题》Python, Java, C++ 解题代码项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Book

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 15:01:44

基于Java Web的博客系统课程设计:从数据库设计到部署实战

简介&#xff1a;这是一份基于Java Web的博客系统课程设计完整资源包&#xff0c;面向计算机相关专业学生、Java初学者及需要完成课程设计的开发者&#xff0c;解决选题难、缺少可运行示例与配套说明的痛点。项目采用MVC开发模式&#xff0c;基于Eclipse/MyEclipse集成开发环境…

作者头像 李华
网站建设 2026/9/16 15:00:19

Harness内存治理:Lua热更脚本的分代池与引用计数实践

1. 这不是“换框架”&#xff0c;而是给游戏脚本做一次精准的内存外科手术你有没有遇到过这样的情况&#xff1a;一个原本跑得挺顺的Lua热更脚本&#xff0c;在接入新功能模块后&#xff0c;内存占用曲线突然像坐上了火箭——GC频率翻倍、帧率偶尔掉点、甚至在低端机上出现偶发…

作者头像 李华
网站建设 2026/9/16 15:00:11

Pentagi:基于Docker+Neo4j+AI Agents的安全知识操作系统

1. 项目概述&#xff1a;Pentagi 是什么&#xff1f;它解决的不是“渗透测试自动化”&#xff0c;而是安全研究范式的迁移 Pentagi 这个名字乍看像一个拼写变体&#xff0c;但结合热搜词 pentagi、penetration testing、ai agents、docker、neo4j &#xff0c;它绝非某个小众…

作者头像 李华
网站建设 2026/9/16 14:59:08

Web3.0测试环境安全攻防实战与防御策略

1. Web3.0测试为何成为攻击重灾区&#xff1f;Web3.0测试环境频繁遭受攻击并非偶然&#xff0c;而是由其技术架构特性与测试方法论缺陷共同导致的系统性风险。2024年链上安全事件造成的23.63亿美元损失中&#xff0c;测试环节暴露的问题占比高达37%&#xff0c;这个数字背后是三…

作者头像 李华
网站建设 2026/9/16 14:58:50

DMC1000B与LabVIEW工程级对接:寄存器映射、DLL调用与实时闭环

简介&#xff1a;本资源是一套面向LabVIEW开发者与自动化控制工程师的DMC1000/DMC1000B数据采集模块实战开发包&#xff0c;聚焦于工业测控与实验室系统集成场景&#xff0c;解决设备在LabVIEW平台下的快速接入、参数配置、实时采集与可视化控制等核心问题。压缩包共37个文件&a…

作者头像 李华