70. Climbing Stairs - Explanation
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: 2Explanation:
1 + 1 = 22 = 2
Example 2:
Input: n = 3
Output: 3Explanation:
1 + 1 + 1 = 31 + 2 = 32 + 1 = 3
Constraints:
1 <= n <= 45
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.
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
- Start from step
0. - Define a recursive function
dfs(i):- If
i == n, return1(one valid way). - If
i > n, return0(invalid path).
- If
- Recursively compute:
dfs(i + 1)→ take 1 stepdfs(i + 2)→ take 2 steps
- Return the sum of both choices.
- The answer is
dfs(0).
Time & Space Complexity
- Time complexity:
- Space complexity:
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
iis always the same whenever we reachi.
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
- Create a cache array
cacheof sizeninitialized with-1. - Define a recursive function
dfs(i):- If
i == n, return1(valid way). - If
i > n, return0(invalid path). - If
cache[i] != -1, return the cached value.
- If
- Otherwise:
- Compute
dfs(i + 1) + dfs(i + 2) - Store the result in
cache[i]
- Compute
- Return
dfs(0)as the final answer.
Time & Space Complexity
- Time complexity:
- Space complexity:
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
- If
n <= 2, returnn. - Create a DP array where
dp[i]= number of ways to reach stepi. - Initialize:
dp[1] = 1dp[2] = 2
- For
ifrom3ton:dp[i] = dp[i - 1] + dp[i - 2]
- Return
dp[n].
Time & Space Complexity
- Time complexity:
- Space complexity:
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
- Initialize two variables:
one-> ways to reach the current steptwo-> ways to reach the previous step
- Start both as
1(base case). - Repeat
n - 1times:- New ways =
one + two - Shift variables forward.
- New ways =
- Return
one.
Time & Space Complexity
- Time complexity:
- Space complexity:
5. Matrix Exponentiation
Intuition
The number of ways to climb stairs follows the Fibonacci sequence:
- To reach step
n, you can come fromn-1orn-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
- Use the Fibonacci matrix:
|1 1|
|1 0|- Raise this matrix to power
nusing binary exponentiation. - Matrix exponentiation repeatedly squares the matrix to reduce work.
- 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:
- Space complexity:
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
- Compute the golden ratio
φ = (1 + √5) / 2and its conjugateψ = (1 − √5) / 2. - Use Binet’s Formula to compute the Fibonacci value directly.
- Since climbing stairs corresponds to
Fib(n+1), adjustnaccordingly. - Round the result to handle floating-point precision.
Time & Space Complexity
- Time complexity:
- Space complexity:
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.
Sign in to join the discussion