CS-Notes 剑指 Offer 10.4 变态跳台阶:从 O(n²) 动态规划到 O(1) 直接求 2^(n-1)
本文基于 CS-Notes 仓库中的「剑指 Offer 10.4 变态跳台阶」题解展开,讲解青蛙一次最多可跳 n 级台阶这一经典动态规划问题的两种解法:先用二维 DP 自底向上推导,再通过数学化简把递推式压缩为等比数列。读完后,你能掌握该题完整的状态转移推导过程、两套可直接复制的 Java 实现、各自的时间/空间复杂度,以及 int 溢出的适用边界,并能与系列前作「跳台阶」「矩形覆盖」对照,看清动态规划题型的演化脉络。
题目描述
一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级……它也可以跳上 n 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
本题是剑指 Offer 第 10 题系列的最后一问,难度递进关系为:
| 题目 | 一次最多可跳 | 结果形式 |
|---|---|---|
| 10.2 矩形覆盖 | 2 格 | 斐波那契数列 f(n) = f(n-1) + f(n-2) |
| 10.3 跳台阶 | 2 级 | 斐波那契数列 f(n) = f(n-1) + f(n-2) |
| 10.4 变态跳台阶 | n 级 | 等比数列 f(n) = 2 · f(n-1) |
前三题共用“把大问题拆成子问题”的动态规划思路;当单跳步数上限从 2 放开到 n 后,状态转移式从“只依赖前两项”变成了“依赖前面所有项”,解法也随之从滚动数组退化为可以直接闭式求解的等比数列。四道题的完整索引见 剑指 Offer 题解 - 目录 中的“动态规划”小节。
解法一:动态规划(O(n²))
按题解给出的 DP 思路:设 dp[i] 为跳上第 i+1 级台阶的跳法总数。由于青蛙可以从任意低台阶一次跳到当前台阶,dp[i] 等于前面所有 dp[j] 之和,再加上“直接从地面一步跳到顶”这一种跳法。参考实现如下(来自 10.4 变态跳台阶):
public int jumpFloorII(int target) {
int[] dp = new int[target];
Arrays.fill(dp, 1);
for (int i = 1; i < target; i++)
for (int j = 0; j < i; j++)
dp[i] += dp[j];
return dp[target - 1];
}
逐行拆解这段代码的细节:
-
Arrays.fill(dp, 1):初始化把每个dp[i]都置 1。其中dp[0] = 1对应“1 级台阶只有 1 种跳法”;后续每轮的+1部分,则代表“从地面直接跳上当前台阶”这一种跳法,它与逐级累加的dp[0] + ... + dp[i-1]共同构成完整转移式:f(i) = 1 + f(0) + f(1) + ... + f(i-1) -
双重循环:外层枚举目标台阶
i,内层把i之前所有台阶的跳法累加进来。由于每计算一个dp[i]都要扫描一次[0, i),总操作量是 1 + 2 + … + (n-1) = O(n²) 次加法。 -
返回
dp[target - 1],即 n 级台阶的总跳法。
复杂度:时间 O(n²),空间 O(n)。以 n = 6 为例验证:f(1)=1,f(2)=2,f(3)=4,f(4)=8,f(5)=16,f(6)=32,与后文 2^(n-1) 的结论完全一致。
解法二:数学推导(O(1))
题解的核心化简在于把“求和型”递推转化为“乘 2 型”递推。
跳上 n-1 级台阶,可以从 n-2 级跳 1 级上去,也可以从 n-3 级跳 2 级上去……,那么:
f(n-1) = f(n-2) + f(n-3) + ... + f(0)
同样,跳上 n 级台阶,可以从 n-1 级跳 1 级上去,也可以从 n-2 级跳 2 级上去……,那么:
f(n) = f(n-1) + f(n-2) + ... + f(0)
两式相减,f(n-2) 到 f(0) 全部抵消,只剩首项:
f(n) - f(n-1) = f(n-1)
即:
f(n) = 2*f(n-1)
所以 f(n) 是一个首项 f(1) = 1、公比为 2 的等比数列,通项直接写成:
f(n) = 2^(n-1)
题解给出的 O(1) 实现:
public int JumpFloorII(int target) {
return (int) Math.pow(2, target - 1);
}
两种解法对比与工程注意
| 维度 | DP 解法 | 数学解法 |
|---|---|---|
| 时间复杂度 | O(n²) | O(1) |
| 空间复杂度 | O(n) | O(1) |
| 正确性依据 | 直接枚举状态转移 | 依赖等比数列推导 |
| 大 n 表现 | 加法累加,int 溢出较慢 | 指数增长,target 超过 32 时 int 结果溢出 |
结合 10.1 斐波那契数列 中展示的空间优化思想(滚动变量只保留必要项)可以推断:如果不用闭式解,DP 解法的内层求和也可以用一个“前缀和”滚动变量维护,把 O(n²) 压到 O(n);但本题既然存在等比数列的闭式解,O(1) 解法是最终形态,这也是该题在面试中主要考察“能否从求和递推推出乘数递推”这一数学洞察力,而非单纯写 DP。
工程上需要注意:2^(n-1) 增长速度极快,int 类型在 target - 1 >= 31 时即超出 2³¹-1 的上限,(int) Math.pow(...) 会溢出得到错误值。因此在实际使用中:
- 若题目保证 n 较小(如面试环境常见 n < 30),可直接使用上面的 O(1) 写法;
- 若 n 很大且要求精确值,应改用
BigInteger计算 2 的幂,或按题目要求对某个模数取余(配合快速幂)。
系列延伸
跳台阶系列是动态规划入门的最佳路径,建议按以下顺序对照阅读,体会状态转移式随约束放宽如何演化:
- 10.1 斐波那契数列:递归 → 数组 DP → O(1) 滚动变量的三次优化;
- 10.2 矩形覆盖:用 2×1 矩形铺 2×n 大矩形,转移式 f(n) = f(n-1) + f(n-2);
- 10.3 跳台阶:一次最多跳 2 级,与矩形覆盖同构;
- 10.4 变态跳台阶:一次最多跳 n 级,化简为等比数列 f(n) = 2^(n-1)。
需要更多动态规划练手题目时,可继续参考仓库中的 Leetcode 题解 - 动态规划,本篇的“状态转移 + 复杂度分析 + 边界注意”的写法可以直接套用到该系列的各题上。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
