Solving the Climbing Stairs problem using recursion, recursion tree analysis, and memoization.
The original problem is here.
You are climbing a staircase that takes $n$ steps to reach the top. Each time you can either climb $1$ or $2$ steps. In how many distinct ways can you climb to the top?
In this brute force approach, we take all possible step combinations (i.e., $1$ and $2$) at every step. At every step, we call the recursive function for taking $1$ step and $2$ steps, and return the sum of the returned values of both branches:
\[\text{climbStairs}(i, n) = \text{climbStairs}(i + 1, n) + \text{climbStairs}(i + 2, n)\]where:
0).1).class Solution:
def climbStairs(self, n: int) -> int:
def climb(i: int, n: int) -> int:
if i > n:
return 0
if i == n:
return 1
return climb(i + 1, n) + climb(i + 2, n)
return climb(0, n)
The recursion tree for $n = 5$ demonstrates how redundant calculations explode exponentially:
(0)
/ \
(1) (2)
/ \ / \
(2) (3) (3) (4)
/ \ / \ / \ / \
(3) (4) (4) (5) (4) (5) (5) (6)
...
Notice that subproblems like (2), (3), and (4) are computed over and over again along different paths.
In the previous approach, we are redundantly calculating the result for every step. Instead, we can store the result of each step in a memo array and directly return the cached result whenever that step is called again.
In this way, we are pruning the recursion tree with the help of the memo array and reducing the size of the recursion tree down to $n$.
(0)
/ \
(1) (2) [cached]
/ \
(2) (3) [cached]
/ \
(3) (4) [cached]
/ \
(4) (5) [base]
/ \
(5) (6)
Every state from $0$ to $n$ is evaluated only once, transforming an exponential search tree into a linear sequence of subproblems.
class Solution:
def climbStairs(self, n: int) -> int:
memo = [0] * (n + 1)
def climb(i: int, n: int) -> int:
if i > n:
return 0
if i == n:
return 1
if memo[i] > 0:
return memo[i]
memo[i] = climb(i + 1, n) + climb(i + 2, n)
return memo[i]
return climb(0, n)
memo array takes $O(n)$ auxiliary space.