爬楼梯 图解题解
这道题到底在问什么
- 输入
- n = 10
- 输出
- 89
最优解:为什么这么做
一句话答案:LeetCode 70 爬楼梯的标准解是动态规划递推 dp[i] = dp[i-1] + dp[i-2]:到第 i 阶的最后一步只能跨 1 或 2 阶,两类互斥穷尽,方案数相加。两个变量滚动,时间 O(n)、空间 O(1),本质是斐波那契。
爬楼梯到底在数什么
楼梯共 n 阶,每步只能上 1 阶或 2 阶,问从地面走到第 n 阶一共有多少种不同的走法。注意这是一道「数方案数」的题:答案是走法的总数,而不是最少步数,也不是某条具体路线。比如 n = 10 时答案是 89,意味着存在 89 条互不相同的上楼顺序。
为什么不能把所有走法枚举一遍
最直觉的思路是把每一步的选择都展开:每一步有跨 1 阶、跨 2 阶两个分支,一路分叉下去,走法总数随 n 指数级膨胀,逐条枚举根本数不完。慢的根源是重复劳动——无数条路线的后半段其实一模一样,却被一遍遍重数。
关键观察藏在「最后一步」上:不管前面怎么走,落到第 i 阶的最后一步只有两种可能——从第 i-1 阶跨 1 步上来,或从第 i-2 阶跨 2 步上来。于是「到第 i 阶的方法数」就落在「到 i-1 阶」和「到 i-2 阶」这两个更小的问题上,递推能写了。
dp 数组怎么定义,dp[0] 为什么是 1
定义 dp[i] 为「从地面走到第 i 阶的方法数」。起点要铺两个:dp[1] = 1,因为到第 1 阶只有跨一步这一种走法;dp[0] = 1,表示站在地面、一步没走也算一种方案。dp[0] 常被误设成 0,一旦设 0,dp[2] = dp[1] + dp[0] 就会漏掉「直接跨 2 阶」这条路,错误还会一路传染,整张表全部偏小。
为什么两条来路直接相加不重不漏
加法成立的依据是分类计数:按「最后一步跨几阶」把到达第 i 阶的所有走法分成两堆——最后跨 1 阶的,前半程恰好是一条到 i-1 阶的走法;最后跨 2 阶的,前半程恰好是一条到 i-2 阶的走法。两堆的最后一步不同,不可能有同一条走法被数两次;而最后一步只有这两种可能,也不会有走法被漏掉。这题是数方案不是求最优,转移用加法而不是 min 或 max——同一张递推表换成取最小值,就变成另一类问题了。
拿 n=10 把递推填一遍
亲手填一遍表就彻底通了。地面 dp[0] = 1,第 1 阶 dp[1] = 1。到第 2 阶:最后一步要么从第 1 阶跨 1、要么从地面跨 2,dp[2] = dp[1] + dp[0] = 2。往后每格都是前两格相加:dp[3] = 3、dp[4] = 5、dp[5] = 8,整串就是 1、1、2、3、5、8、13、21、34、55、89,到 dp[10] = 89,正是答案。滚动优化时只留最近两个数,用 a, b = b, a + b(把 a 换成旧的 b、b 换成两者之和)每轮向前挪一格,整张表根本不必存下来。
复杂度多少,和斐波那契什么关系
从 dp[2] 递推到 dp[n],每格只做一次加法,时间 O(n);又因为 dp[i] 只依赖前两项,不必存整个数组,用两个变量轮替即可,空间 O(1)。
递推式 dp[i] = dp[i-1] + dp[i-2] 加上 dp[0] = dp[1] = 1,正是斐波那契数列,所以爬楼梯常被叫作斐波那契的「面试皮肤」。边界上留意 n = 1 要能直接返回 1,循环从第 2 阶才开始。
▶ 动画逐步走查(共 23 步)——想跟着动画一帧帧对照就展开
- 3记住这条转移式:到第 i 阶的方法数 = 到 i-1 阶的方法数 + 到 i-2 阶的方法数。下面每一格都在套它。
- 4下行 dp 待填,列号 0..10 是阶数。从最左边的两个起点开始。
- 5dp[0]=1:站在地面(还没爬)算 1 种方法,这是计数的起点。
- 6dp[1]=1:到第 1 阶只有「走 1 步」这 1 种走法。两个起点都先定下来。
- 7算 dp[2]:两条来路——从第 1 阶跨 1 步上来有 1 种,从第 0 阶跨 2 步上来有 1 种。这两批走法没有重复。
- 8把两条来路相加:1 + 1 = 2,填进 dp[2]。到第 2 阶共有 2 种走法。
- 9算 dp[3]:两条来路——从第 2 阶跨 1 步上来有 2 种,从第 1 阶跨 2 步上来有 1 种。这两批走法没有重复。
- 10把两条来路相加:2 + 1 = 3,填进 dp[3]。到第 3 阶共有 3 种走法。
- 11算 dp[4]:两条来路——从第 3 阶跨 1 步上来有 3 种,从第 2 阶跨 2 步上来有 2 种。这两批走法没有重复。
- 12把两条来路相加:3 + 2 = 5,填进 dp[4]。到第 4 阶共有 5 种走法。
- 13算 dp[5]:两条来路——从第 4 阶跨 1 步上来有 5 种,从第 3 阶跨 2 步上来有 3 种。这两批走法没有重复。
- 14把两条来路相加:5 + 3 = 8,填进 dp[5]。到第 5 阶共有 8 种走法。
- 15算 dp[6]:两条来路——从第 5 阶跨 1 步上来有 8 种,从第 4 阶跨 2 步上来有 5 种。这两批走法没有重复。
- 16把两条来路相加:8 + 5 = 13,填进 dp[6]。到第 6 阶共有 13 种走法。
- 17算 dp[7]:两条来路——从第 6 阶跨 1 步上来有 13 种,从第 5 阶跨 2 步上来有 8 种。这两批走法没有重复。
- 18把两条来路相加:13 + 8 = 21,填进 dp[7]。到第 7 阶共有 21 种走法。
- 19算 dp[8]:两条来路——从第 7 阶跨 1 步上来有 21 种,从第 6 阶跨 2 步上来有 13 种。这两批走法没有重复。
- 20把两条来路相加:21 + 13 = 34,填进 dp[8]。到第 8 阶共有 34 种走法。
- 21算 dp[9]:两条来路——从第 8 阶跨 1 步上来有 34 种,从第 7 阶跨 2 步上来有 21 种。这两批走法没有重复。
- 22把两条来路相加:34 + 21 = 55,填进 dp[9]。到第 9 阶共有 55 种走法。
- 23算 dp[10]:两条来路——从第 9 阶跨 1 步上来有 55 种,从第 8 阶跨 2 步上来有 34 种。这两批走法没有重复。
- 24把两条来路相加:55 + 34 = 89,填进 dp[10]。到第 10 阶共有 89 种走法。
- 25最右 dp[10]=89 就是爬到第 10 阶的总走法数。整张表其实就是斐波那契数列。
⚠️ 容易写错的地方
✗ 错:dp[0] 设成 0
✓ 对:dp[0]=1
站在地面算 1 种方法,否则全表恒为 0
✗ 错:把加和写成 min/max
✓ 对:dp[i]=dp[i-1]+dp[i-2]
本题是数方案数,不是求最优值
✗ 错:漏掉 n=1
✓ 对:n=1 直接返回 1
循环从 i=2 起,base 要先铺好
完整代码(Python / C++ / Java)
Python
def climbStairs(n):
a, b = 1, 1 # dp[i-2], dp[i-1]
for _ in range(2, n + 1):
a, b = b, a + b
return bC++
int climbStairs(int n){
int a = 1, b = 1;
for(int i = 2; i <= n; ++i){
int c = a + b;
a = b; b = c;
}
return b;
}Java
int climbStairs(int n){
int a = 1, b = 1;
for(int i = 2; i <= n; i++){
int c = a + b;
a = b; b = c;
}
return b;
}复杂度
时间
O(n)
一遍线性递推,每格 O(1)
空间
O(1)
只需前两项滚动
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 爬楼梯 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
爬楼梯为什么用加法,而不是像最优化题那样取 min 或 max?+
这题数的是方案总数,不是最优值。按最后一步跨 1 还是跨 2,把走法分成两堆互不重叠、又不遗漏,总数自然是两堆相加。如果题目改成求最少步数或最小花费,同一张递推表才把加法换成取 min。
dp[0] 到底该设成 1 还是 0?+
设 1。dp[0] 表示站在地面、一步没走,算一种「空走法」;只有这样,dp[2] = dp[1] + dp[0] = 2 才能把「从地面直接跨 2 阶」这条路算进去。设成 0 会让整张表偏小,而且错误会一路向上传染。
如果每步能跨 1、2、3 阶(甚至 1 到 k 阶)怎么改?+
把转移从两项相加推广成前 k 项相加:dp[i] = dp[i-1] + dp[i-2] + … + dp[i-k],用一个长度为 k 的滑动窗口维护这几项的和即可,思路完全一样。这也是「爬楼梯」系列题的通用套路。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 爬楼梯 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。