Climbing Stairs

Problem

Given an integer n representing the number of steps, return the number of distinct ways to climb to the top, where each time you can either climb 1 or 2 steps.

  • 1 ≤ n ≤ 45

Example

Input: n = 3
Output: 3

The possible ways to climb 3 steps are: (1 step + 1 step + 1 step), (1 step + 2 steps), and (2 steps + 1 step). The algorithm uses recursion with memoization to count these combinations efficiently. Starting from step 3, it breaks down the problem into counting ways to reach steps 2 and 1, which are computed recursively and cached to avoid redundant calculations.

Approach

Straightforward Solution

A naive recursive solution explores all possible sequences of 1-step and 2-step moves, leading to exponential time complexity due to repeated calculations of the same subproblems.

Core Observation

The number of ways to reach step i depends solely on the ways to reach steps i-1 and i-2, reflecting the problem's optimal substructure and overlapping subproblems.

Path to Optimal

Recognizing the overlapping subproblems, memoization is introduced to cache results of subproblems, transforming the exponential recursion into a linear time dynamic programming solution. This approach stores the number of ways to reach each step, avoiding redundant computations.

Optimal Approach

Implement a top-down recursive function with memoization that returns the number of ways to reach step i by summing the ways to reach steps i-1 and i-2. The memo dictionary caches intermediate results, ensuring each subproblem is solved once, resulting in O(n) time and space complexity.

Time

O(n)

Each state i from 0 to n is computed once and stored in the memo dictionary, preventing repeated calculations and resulting in linear time complexity.

Space

O(n)

The memo dictionary stores results for each step up to n, and the recursion stack can go as deep as n, leading to O(n) auxiliary space usage.

Pattern Spotlight

Dynamic Programming (Top-Down Memoization)

When a problem's solution depends on overlapping subproblems with optimal substructure, use recursion with memoization to cache intermediate results and avoid redundant computations, converting exponential recursion into efficient linear time.

Solution

1class Solution:
2 def climbStairs(self, n: int) -> int:
3 memo = {}
4
5 def dp(i):
6 if i <= 1:
7 return 1
8 if i in memo:
9 return memo[i]
10
11 memo[i] = dp(i - 1) + dp(i - 2)
12 return memo[i]
13
14 return dp(n)

Step-by-Step Solution

1

Implement Memoized Recursive Function to Count Ways

3memo = {}
5def dp(i):
6 if i <= 1:
7 return 1
8 if i in memo:
9 return memo[i]
11 memo[i] = dp(i - 1) + dp(i - 2)
12 return memo[i]

Objective

To define a recursive function that computes the number of ways to reach step i, using memoization to cache and reuse results.

Key Insight

The problem breaks down into counting ways to reach the previous two steps, reflecting the Fibonacci sequence structure. Memoization ensures that once the number of ways to reach a step is computed, it is stored and reused, preventing exponential recomputation and enabling efficient top-down dynamic programming.

Interview Quick-Check

Core Logic

The recursive function returns 1 for base cases (i <= 1) and otherwise sums the results of dp(i-1) and dp(i-2), caching results in memo to avoid redundant calls.

State & Boundaries

Base cases handle steps 0 and 1, returning 1 as there is exactly one way to stand still or take a single step.

Common Pitfalls & Bugs

Forgetting to check the memo before recursion leads to exponential time due to repeated subproblem evaluations.

Complexity

Memoization reduces the time complexity from O(2^n) to O(n) by ensuring each subproblem is solved once.

2

Return the Total Number of Ways to Climb n Steps

14return dp(n)

Objective

To initiate the recursive computation for the full problem and return the final result.

Key Insight

The final answer is the number of ways to reach step n, obtained by calling the memoized recursive function. This single call triggers the entire top-down dynamic programming process, leveraging cached results for efficiency.

Interview Quick-Check

Core Logic

Calling dp(n) starts the recursion and returns the total number of distinct ways to climb n steps.

Line Analysis

This solution has 5 Critical lines interviewers watch for.

Line 8 Critical
if i in memo:

Check if the result for step i is already cached in memo.

This lookup prevents recomputation of subproblems, which is the key optimization that reduces time complexity from exponential to linear.

Line 11 Critical
memo[i] = dp(i - 1) + dp(i - 2)

Compute the number of ways to reach step i by summing ways to reach steps i-1 and i-2.

This recurrence captures the problem's optimal substructure, reflecting that each step can be reached from the two preceding steps, forming the Fibonacci-like relation.

Line 6 Critical
if i <= 1:

Return 1 if i is 0 or 1, representing base cases.

These base cases define the minimal steps scenarios where there is exactly one way to climb, serving as the foundation for the recursion.

Line 7 Critical
return 1

Return 1 for base cases where i <= 1.

This line ensures the recursion terminates correctly and provides the correct count for the smallest subproblems, preventing infinite recursion.

Line 9 Critical
return memo[i]

Return the cached result for step i if available.

Returning the memoized value immediately avoids unnecessary recursive calls, ensuring efficient reuse of previously computed results.

Line 3 Core
memo = {}

Initialize a dictionary to cache computed results for subproblems.

This memo dictionary is essential for storing intermediate results, enabling the algorithm to avoid redundant recursive calls and achieve linear time complexity.

Line 14 Core
return dp(n)

Invoke the recursive function for step n and return the final count.

This call initiates the entire top-down dynamic programming process and returns the total number of distinct ways to climb n steps.

Line 5 Core
def dp(i):

Define the recursive function dp that computes ways to reach step i.

Encapsulating the problem in a recursive function with memoization captures the problem's optimal substructure and overlapping subproblems, forming the basis of the dynamic programming solution.

Line 12 Core
return memo[i]

Return the computed number of ways for step i.

Returning the memoized result completes the recursive call, propagating the computed value up the call stack.

Test Your Understanding

Why does memoization reduce the time complexity from exponential to linear in this problem?

Because memoization caches the results of subproblems, each unique state is computed only once, eliminating redundant recursive calls and thus reducing the overall time complexity to O(n).

Related Problems

Dynamic Programming pattern

Don't just read it. Drill it.

Reconstruct Climbing Stairs from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.

Unlock the Climbing Stairs drill

or drill a free problem