每天学习一点算法 2025/12/25
题目:爬楼梯
假设你正在爬楼梯。需要
n阶你才能到达楼顶。每次你可以爬
1或2个台阶。你有多少种不同的方法可以爬到楼顶呢?
爬楼梯是一道十分经典的动态规划题目,我记得大学老师还讲过这道题,虽然已经忘的差不多了,我们首先来分析这题目,我们先从小数推导规律,设 fn(i) = 爬到第i阶的方法数
-
爬 1 阶楼梯:只有一种方法,
fn(1) = 1 -
爬 2 阶楼梯:有
1+1、2两种方法,fn(2) = 2 -
爬 3 阶楼梯:我们换一个思考方式,如果我们登上了第 3 阶楼梯的最后一步爬了 1 个台阶,那这种爬 3 阶楼梯的方法就和爬 2 阶楼梯的方法一样,如果最后一步爬了 2 个台阶,那这种爬 3 阶楼梯的方法就和爬 1 阶楼梯的方法一样。这两种爬最后一步的方法加在一起就是爬 3 阶的楼梯的方法数,
fn(3) = f(2) + f(1) = 3 -
爬 n 阶楼梯:从这个爬 3 阶楼梯的分析,我们很容易得出,爬 n 阶楼梯的方法就是,爬 n - 1 阶楼梯(最后一步是爬 1 个台阶)的方法加上爬 n - 2 阶楼梯(最后一步是爬 2 个台阶)的方法,
fn(n) = fn(n - 1) + fn(n - 2)
分析得出了最终的公式: fn(n) = fn(n - 1) + fn(n - 2)
-
最容易想到的就是递归
typescriptfunction climbStairs(n: number): number { if (n === 1) return 1 if (n === 2) return 2 return climbStairs(n - 1) + climbStairs(n - 2) };此法虽然很容易想到但是数字太大的时候会超时,而且存在大量的重复运算,比如我们在计算爬 5 阶楼梯和爬 4 阶的方法时,都会计算爬 3 阶楼梯的方法。
-
上面的递归存在重复计算,我们可以用记忆化递归的方法,用一个数组存储已经计算过的结果,这样就可以有效的减少时间复杂度了
typescriptfunction climbStairs(n: number): number { const memoSet: number[] = [1, 1, 2] // 初始填入 0 1 2 阶的爬楼方法数 function auxiliary (n: number, memo: number[]): number { // 如果已有爬楼的计算结果直接取出不再重复递归 if (memo[n]) return memo[n] // 递归计算爬楼方法并存储到记忆数组中 memo[n] = auxiliary(n - 1, memo) + auxiliary(n - 2, memo) return memo[n] } // 传入记忆数组 return auxiliary(n, memoSet) }; -
接下来讲一下动态规划的解题方法,动态规划(Dynamic Programming,简称 DP)核心是用空间换时间,避免重复计算,本质是:把一个复杂的大问题,拆解成若干个可解决的、不重复的小问题,先解决小问题并记录答案(存起来),再用小问题的答案推导大问题的答案。
什么意思呢?
- 递归是从目标值递归拆解成小问题,从目标
n递归拆解成n-1和n-2。 - 动态规划是从最小的初始条件开始,一步步算到目标值(比如从
i=3算到i=n)
typescriptfunction climbStairs(n: number): number { // 处理边界 1 2 阶楼梯爬法 if (n <= 2) return n; const memoSet: number[] = [1, 1, 2] // 初始填入 0 1 2 阶的爬楼方法数 // 循环计算n阶爬楼方法 for (let i = 3; i <= n; i++) { memoSet[i] = memoSet[i - 1] + memoSet[i - 2] } // 返回计算结果 return memoSet[n] }; - 递归是从目标值递归拆解成小问题,从目标
-
上面的方法我们看出,前面已经用过的数据其实已经可以丢弃了,那我们还可以优化一下空间复杂度,用两个变量存储
n - 1和n - 2阶的方法数。typescriptfunction climbStairs(n: number): number { // 处理边界 1 2 阶楼梯爬法 if (n <= 2) return n; let i = 1 // n - 2 初始为1阶楼梯爬法 let j = 2 // n - 3 初始为2阶楼梯爬法 let current = 0 // 存储当前阶的方法数 // 循环计算爬 n 阶方法数 for (let k = 3; k <= n; k++) { current = i + j i = j j = current } return current };
题目来源:力扣(LeetCode)