70. Climbing Stairs - Explanation

Problem Link

Description

You are given an integer n representing the number of steps to reach the top of a staircase. You can climb with either 1 or 2 steps at a time.

Return the number of distinct ways to climb to the top of the staircase.

Example 1:

Input: n = 2

Output: 2

Explanation:

  1. 1 + 1 = 2
  2. 2 = 2

Example 2:

Input: n = 3

Output: 3

Explanation:

  1. 1 + 1 + 1 = 3
  2. 1 + 2 = 3
  3. 2 + 1 = 3

Constraints:

  • 1 <= n <= 30


Topics

Recommended Time & Space Complexity

You should aim for a solution as good or better than O(n) time and O(n) space, where n is the number of steps.


Hint 1

At each step, we have two choices: climb one step or climb two steps. We can solve this by considering both options and picking the minimum using recursion. However, this results in O(2^n) time complexity. Can you think of a better approach? Perhaps, try to avoid the repeated work of calling recursion more than once with same parameters.


Hint 2

This is a Dynamic Programming problem. We can use Memoization to avoid repeated work. Create an n-sized array cache to store the results of recursive calls. When the recursion is called with specific parameters, return the stored value if it has already been computed. How would you implement this?


Hint 3

We start the initial recursion with i = 0, indicating that we are at position i. We first check if the current recursion with the given i is already cached. If it is, we immediately return the stored value. Otherwise, we perform the recursion, store the result in the cache, and then return it. Can you think of the base condition to stop the recursion?


Hint 4

At each recursion, we perform two recursive calls: one for climbing one step and another for climbing two steps. The minimum return value between the two is the result for the current recursion. The base condition is to return 0 if i == n. This is a one-dimensional dynamic programming problem, which can be further optimized using more advanced techniques.


Company Tags


Prerequisites

Before attempting this problem, you should be comfortable with:

  • Recursion - Understanding recursive function calls and base cases
  • Dynamic Programming - Memoization (top-down) and tabulation (bottom-up) approaches to optimize overlapping subproblems
  • Fibonacci Sequence - Recognizing patterns where each value depends on the previous two values

1. Recursion

Intuition

At every step, you have two choices:

  • climb 1 step
  • climb 2 steps

So from any step i, the number of ways to reach the top is:

  • ways from i + 1
  • plus ways from i + 2

This naturally forms a binary recursion tree where we try all possible paths.

  • If we land exactly on step n, that path counts as 1 valid way
  • If we cross n, it’s an invalid path

This is a classic example of exploring all possibilities using recursion.

Algorithm

  1. Start from step 0.
  2. Define a recursive function dfs(i):
    • If i == n, return 1 (one valid way).
    • If i > n, return 0 (invalid path).
  3. Recursively compute:
    • dfs(i + 1) → take 1 step
    • dfs(i + 2) → take 2 steps
  4. Return the sum of both choices.
  5. The answer is dfs(0).
class Solution:
    def climbStairs(self, n: int) -> int:

        def dfs(i):
            if i >= n:
                return i == n
            return dfs(i + 1) + dfs(i + 2)

        return dfs(0)

Time & Space Complexity

  • Time complexity: O(2n)O(2 ^ n)
  • Space complexity: O(n)O(n)

2. Dynamic Programming (Top-Down)

Intuition

This is the optimized version of the recursive solution.

The key observation is:

  • While exploring choices (1 step or 2 steps), the same subproblems repeat many times.
  • For example, the number of ways from step i is always the same whenever we reach i.

So instead of recomputing, we store the result the first time we compute it and reuse it later.
This is exactly what Top-Down Dynamic Programming (Memoization) does.

We still think recursively, but we avoid redundant work.

Algorithm

  1. Create a cache array cache of size n initialized with -1.
  2. Define a recursive function dfs(i):
    • If i == n, return 1 (valid way).
    • If i > n, return 0 (invalid path).
    • If cache[i] != -1, return the cached value.
  3. Otherwise:
    • Compute dfs(i + 1) + dfs(i + 2)
    • Store the result in cache[i]
  4. Return dfs(0) as the final answer.
class Solution:
    def climbStairs(self, n: int) -> int:
        cache = [-1] * n
        def dfs(i):
            if i >= n:
                return i == n
            if cache[i] != -1:
                return cache[i]
            cache[i] = dfs(i + 1) + dfs(i + 2)
            return cache[i]

        return dfs(0)

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

3. Dynamic Programming (Bottom-Up)

Intuition

To reach step i, you can only come from:

  • step i - 1 (1 step)
  • step i - 2 (2 steps)

So the total ways to reach step i is the sum of ways to reach the previous two steps.
This forms a Fibonacci-like pattern.

Algorithm

  1. If n <= 2, return n.
  2. Create a DP array where dp[i] = number of ways to reach step i.
  3. Initialize:
    • dp[1] = 1
    • dp[2] = 2
  4. For i from 3 to n:
    • dp[i] = dp[i - 1] + dp[i - 2]
  5. Return dp[n].
class Solution:
    def climbStairs(self, n: int) -> int:
        if n <= 2:
            return n
        dp = [0] * (n + 1)
        dp[1], dp[2] = 1, 2
        for i in range(3, n + 1):
            dp[i] = dp[i - 1] + dp[i - 2]
        return dp[n]

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

4. Dynamic Programming (Space Optimized)

Intuition

At any step, the number of ways depends only on the previous two steps.
So instead of storing all values in a DP array, we can just keep two variables that represent:

  • ways to reach the previous step
  • ways to reach the step before that

This is the same Fibonacci idea, but optimized to use constant space.

Algorithm

  1. Initialize two variables:
    • one -> ways to reach the current step
    • two -> ways to reach the previous step
  2. Start both as 1 (base case).
  3. Repeat n - 1 times:
    • New ways = one + two
    • Shift variables forward.
  4. Return one.
class Solution:
    def climbStairs(self, n: int) -> int:
        one, two = 1, 1

        for i in range(n - 1):
            temp = one
            one = one + two
            two = temp

        return one

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(1)O(1)

5. Matrix Exponentiation

Intuition

The number of ways to climb stairs follows the Fibonacci sequence:

  • To reach step n, you can come from n-1 or n-2
  • So, ways(n) = ways(n-1) + ways(n-2)

Fibonacci numbers can be computed efficiently using matrix exponentiation, which reduces the time from linear to logarithmic.

Algorithm

  1. Use the Fibonacci matrix:
|1 1|
|1 0|
  1. Raise this matrix to power n using binary exponentiation.
  2. Matrix exponentiation repeatedly squares the matrix to reduce work.
  3. The value at position [0][0] of the final matrix is the answer.
class Solution:
    def climbStairs(self, n: int) -> int:
        if n == 1:
            return 1

        def matrix_mult(A, B):
            return [[A[0][0] * B[0][0] + A[0][1] * B[1][0],
                     A[0][0] * B[0][1] + A[0][1] * B[1][1]],
                    [A[1][0] * B[0][0] + A[1][1] * B[1][0],
                     A[1][0] * B[0][1] + A[1][1] * B[1][1]]]

        def matrix_pow(M, p):
            result = [[1, 0], [0, 1]]
            base = M

            while p:
                if p % 2 == 1:
                    result = matrix_mult(result, base)
                base = matrix_mult(base, base)
                p //= 2

            return result

        M = [[1, 1], [1, 0]]
        result = matrix_pow(M, n)
        return result[0][0]

Time & Space Complexity

  • Time complexity: O(logn)O(\log n)
  • Space complexity: O(1)O(1)

6. Math

Intuition

The number of ways to climb stairs follows the Fibonacci sequence.
There is a closed-form mathematical formula (called Binet’s Formula) that directly computes the nth Fibonacci number using powers and square roots, without loops or recursion.

This works because Fibonacci numbers can be expressed using two constants derived from the golden ratio.

Algorithm

  1. Compute the golden ratio φ = (1 + √5) / 2 and its conjugate ψ = (1 − √5) / 2.
  2. Use Binet’s Formula to compute the Fibonacci value directly.
  3. Since climbing stairs corresponds to Fib(n+1), adjust n accordingly.
  4. Round the result to handle floating-point precision.
class Solution:
    def climbStairs(self, n: int) -> int:
        sqrt5 = math.sqrt(5)
        phi = (1 + sqrt5) / 2
        psi = (1 - sqrt5) / 2
        n += 1
        return round((phi**n - psi**n) / sqrt5)

Time & Space Complexity

  • Time complexity: O(logn)O(\log n)
  • Space complexity: O(1)O(1)

Common Pitfalls

Off-by-One Error in Base Cases

Initializing DP incorrectly or misunderstanding what dp[0] represents. For this problem, dp[1] = 1 (one way to climb 1 step) and dp[2] = 2 (two ways to climb 2 steps). Starting the loop from index 1 instead of 3 overwrites these base cases.

# Wrong: loop starts too early
for i in range(1, n + 1):
    dp[i] = dp[i-1] + dp[i-2]

# Correct: preserve base cases
for i in range(3, n + 1):
    dp[i] = dp[i-1] + dp[i-2]

Not Handling n = 1 or n = 2 Edge Cases

Attempting to access dp[n-1] or dp[n-2] when n is 1 or 2 without proper base case handling, causing index out of bounds errors. Always check if n <= 2: return n before running the general loop.