509. 斐波那契数(Fibonacci Number)¶
频次 ★★★ · 难度 🟢 · 高频:全厂
题目¶
计算第 n 个斐波那契数 F(n),F(0)=0, F(1)=1, F(n)=F(n-1)+F(n-2)。
示例:
思路¶
DP 三部曲:定义 dp[i] = 第 i 个斐波那契数;递推 dp[i] = dp[i-1] + dp[i-2];压缩到 O(1) 空间(滚动变量)。
也是 DP 入门题,演示"如何从递归到 DP 到空间优化"。
代码¶
// DP 滚动变量 O(n) / O(1)
public int fib(int n) {
if (n <= 1) return n;
int a = 0, b = 1; // F(0), F(1)
for (int i = 2; i <= n; i++) {
int c = a + b;
a = b;
b = c;
}
return b;
}
DP 五部曲自查表(509 = 空白模板,每题照此自问)¶
| 步 | 问题 | 509 答案 |
|---|---|---|
| ① 状态定义 | dp[i] 是什么(一句话说死,含"以…/前 i 个"约束) | dp[i] = F(i) 本身 |
| ② 转移方程 | 从哪些更小状态来 | dp[i] = dp[i-1] + dp[i-2](递归式抄题即转移) |
| ③ 初始化 | 递推算不出的地基 | dp[0]=0、dp[1]=1(新手常错成 dp[1]=0 → 全表错位) |
| ④ 遍历顺序 | 保依赖就绪 | i 从 2 起(dp[-1] 不存在,0/1 已手工) |
| ⑤ 手推验证 | 拿小 case 对 | F(4):0,1,1,2,3 |
三种实现的面试口答三连¶
- 朴素递归 O(2ⁿ):F(30) 重复计算十亿次级——不可交
- 记忆化递归 O(n)/O(n):自顶向下,Map 缓存——同一个"状态+转移"
- 迭代 DP O(n):自底向上,dp 数组可滚动 O(1)(微博中 a,b 双变量,53 的 curSum/maxSum 是同手法)
工具坑:computeIfAbsent 里递归改自身¶
HashMap 的 computeIfAbsent(lambda) 在计算期间持有内部锁,lambda 里递归再往同一个 map 塞值 → ConcurrentModificationException。记忆化递归写 Map 必须老老实实 containsKey 检查 + put(本章骨架已实测验证过这个坑)。
复杂度¶
- 时间:O(n)
- 空间:O(1)
边界条件¶
- n = 0:返回 0
- n = 1:返回 1
- n 较大(如 ≤ 30):直接迭代;n 极大时需矩阵快速幂 O(log n)
变式¶
- 70. 爬楼梯:实质是斐波那契(dp[1]=1, dp[2]=2)
- 矩阵快速幂:O(log n) 解法,面试中提一句证明见识
易错点¶
- 递归写法指数爆炸(O(2^n)),面试中不要直接写递归,用迭代或记忆化
- 滚动变量注意 a, b 的初始值——F(0)=0, F(1)=1
面试追问¶
- 如何用 O(log n) 解? 矩阵快速幂:
[[F(n), F(n-1)],[F(n-1), F(n-2)]] = [[1,1],[1,0]]^(n-1) - 递归加记忆化怎么写? 用 HashMap 或数组保存已计算的值,时间 O(n) 空间 O(n)
关联题¶
- 同套路:70. 爬楼梯 —— 同款递推,初始值不同
- 进阶:746. 使用最小花费爬楼梯 —— 带 cost 的变式
- 知识点:DP 入门 + 滚动变量优化见动态规划