爬楼梯
爬楼梯问题是一个经典的动态规划问题,通常表述为:一个人站在楼梯的底部,每次可以选择爬 1 级或 2 级楼梯,问到达楼顶有多少种不同的方式。
问题分析
-
每次可以选择向上爬 1 层楼梯,或者 2 层楼梯。
-
假设我们在第
n
层楼梯,那么在到达第n
层楼梯时,前一步可以是从第n-1
层楼梯爬上来的,或者从第n-2
层楼梯跳上来的,从而得出下面的公式
f(n)=f(n−1)+f(n−2)
假设要计算 f(5)
(到达 5 层楼梯的方式数):
-
f(5)
=f(4)
+f(3)
-
f(4)
=f(3)
+f(2)
-
f(3)
=f(2)
+f(1)
-
f(2)
= 2 -
f(1)
= 1
边界条件
-
f(0) = 1
:站在地面上,不需要爬任何楼梯,只有1种方式——什么都不做。 -
f(1) = 1
:只有1层楼梯时,只能爬1步——1种方式。 -
f(2) = 2
:有两层楼梯时,可以选择1次爬两层,或者2次、每次爬1层——两种方式。
代码实现
简单递归法:
function climbStairs(n) { // 基本情况 if (n === 0) return 0; if (n === 1) return 1; if (n === 2) return 2; // 递归 return climbStairs(n - 1) + climbStairs(n - 2); }
递归存在的问题:
重复计算许多相同的子问题,效率较低。例如,climbStairs(5)
会分别调用 climbStairs(4)
和 climbStairs(3)
,而 climbStairs(4)
和 climbStairs(3)
又会分别调用 climbStairs(3)
和 climbStairs(2)
,以此类推,造成重复计算。
改进的递归实现
为了避免重复计算,可以使用 记忆化(Memoization)技巧,将已经计算过的结果存储起来,下次需要时直接返回,避免重复计算,从而将时间复杂度降低到 O(n)。
1 function climbStairs(n) { 2 // 使用一个对象存储已经计算过的结果 3 const memo = {}; 4 5 function helper(n) { 6 // 如果已经计算过,直接返回结果 7 if (n === 0) return 0; 8 if (n === 1) return 1; 9 if (n === 2) return 2; 10 11 // 如果之前没有计算过,进行递归并缓存结果 12 if (memo[n] === undefined) { 13 memo[n] = helper(n - 1) + helper(n - 2); 14 } 15 16 return memo[n]; 17 } 18 19 return helper(n); 20 }