Min Cost Climbing Stairs

Problem

Given an integer array cost where cost[i] is the cost of step i, return the minimum cost to reach the top of the floor, where you can start from step 0 or step 1 and move either one or two steps at a time.

  • 2 ≤ cost.length ≤ 1000
  • 0 ≤ cost[i] ≤ 999

Example

Input: cost = [10, 15, 20]
Output: 15

Starting at step 1 (cost 15) and then climbing two steps reaches the top with total cost 15, which is less than starting at step 0 (cost 10) and climbing steps with total cost 30. The recursion explores both options at each step, memoizing results to avoid repeated calculations. For example, dp(3) computes the minimum cost to reach the top from step 3, which is 0 since the top is beyond the last step. dp(2) computes min(cost[1] + dp(1), cost[0] + dp(0)) = min(15 + 0, 10 + 0) = 10, and so forth, building up the solution.

Approach

Straightforward Solution

A brute-force recursive approach tries all possible paths by exploring one or two steps at a time without caching, leading to exponential time complexity due to repeated calculations.

Core Observation

The minimum cost to reach step i depends only on the minimum costs to reach steps i-1 and i-2 plus the cost of those steps, revealing overlapping subproblems and optimal substructure.

Path to Optimal

Recognizing the overlapping subproblems, memoization stores the minimum cost to reach each step once computed, preventing redundant work. The problem reduces to a top-down dynamic programming approach where dp(i) returns the minimum cost to reach step i, computed as the minimum of dp(i-1) + cost[i-1] and dp(i-2) + cost[i-2].

Optimal Approach

Implement a recursive dp function with memoization that returns the minimum cost to reach step i. Base cases return 0 for steps 0 and 1. For other steps, recursively compute dp(i-1) and dp(i-2), add the respective costs, and memoize the minimum. The final answer is dp(len(cost)), representing the minimum cost to reach the top beyond the last step.

Time

O(n)

Each state dp(i) is computed once and stored in the memo dictionary, resulting in linear time proportional to the number of steps.

Space

O(n)

The memo dictionary stores results for each step, requiring O(n) auxiliary space. The recursion stack depth is also O(n) in the worst case.

Pattern Spotlight

Dynamic Programming (Top-Down Memoization)

When a problem involves making a sequence of decisions with overlapping subproblems and optimal substructure, use a recursive function with memoization to cache intermediate results and avoid redundant computations.

Solution

1class Solution:
2 def minCostClimbingStairs(self, cost: list[int]) -> int:
3 memo = {}
4
5 def dp(i):
6 if i <= 1:
7 return 0
8 if i in memo:
9 return memo[i]
10
11 down_one = cost[i - 1] + dp(i - 1)
12 down_two = cost[i - 2] + dp(i - 2)
13 memo[i] = min(down_one, down_two)
14 return memo[i]
15
16 return dp(len(cost))

Step-by-Step Solution

1

Initialize Memoization Cache for Subproblem Results

3memo = {}

Objective

To create a dictionary that caches the minimum cost to reach each step, enabling efficient reuse of computed results.

Key Insight

Memoization transforms the naive recursive solution into an efficient dynamic programming approach by storing results of subproblems. This prevents exponential recomputation and ensures each state is solved once.

Interview Quick-Check

Core Logic

The memo dictionary caches dp(i) results, enabling O(1) retrieval and preventing redundant recursive calls.

Common Pitfalls & Bugs

Forgetting to memoize results leads to exponential time complexity due to repeated subproblem evaluations.

2

Recursively Compute Minimum Cost with Memoization

5def dp(i):
6 if i <= 1:
7 return 0
8 if i in memo:
9 return memo[i]
11 down_one = cost[i - 1] + dp(i - 1)
12 down_two = cost[i - 2] + dp(i - 2)
13 memo[i] = min(down_one, down_two)
14 return memo[i]

Objective

To define a recursive function dp(i) that returns the minimum cost to reach step i, using memoization to avoid redundant computations.

Key Insight

The recursive function exploits the problem's optimal substructure: the cost to reach step i depends only on the costs to reach steps i-1 and i-2 plus their respective costs. By caching results, the recursion efficiently builds up the solution from base cases.

Interview Quick-Check

Core Logic

The dp function returns 0 for base cases (steps 0 and 1), and for other steps, returns the minimum of dp(i-1) + cost[i-1] and dp(i-2) + cost[i-2], memoizing the result.

State & Boundaries

Base cases dp(0) and dp(1) return 0 because starting at these steps incurs no prior cost.

Common Pitfalls & Bugs

Incorrect base cases or missing memoization checks can cause infinite recursion or exponential time.

3

Return Minimum Cost to Reach the Top

16return dp(len(cost))

Objective

To compute and return the minimum cost to reach the top of the floor, which is beyond the last step.

Key Insight

Calling dp with the index equal to the length of the cost array represents reaching the top beyond the last step. This triggers the recursive computation of the minimum cost path considering all valid step sequences.

Interview Quick-Check

Core Logic

The final answer is dp(len(cost)), representing the minimum cost to reach the top beyond the last step.

Line Analysis

This solution has 3 Critical lines interviewers watch for.

Line 6 Critical
if i <= 1:

Return 0 for base cases where i is 0 or 1.

Starting at step 0 or 1 incurs no prior cost, so the minimum cost to reach these steps is zero, establishing the base cases for recursion.

Line 7 Critical
return 0

Check if the result for step i is already memoized.

This lookup prevents redundant computation by returning cached results immediately, a key optimization in memoized recursion.

Line 13 Critical
memo[i] = min(down_one, down_two)

Memoize the minimum of the two computed costs for step i.

Storing the minimal cost for step i enables efficient reuse in future recursive calls, embodying the dynamic programming principle.

Line 3 Core
memo = {}

Initialize a memoization dictionary to cache computed minimum costs for each step.

This dictionary is essential to store intermediate results, preventing redundant recursive calls and reducing time complexity from exponential to linear.

Line 8 Core
if i in memo:

Return the cached minimum cost for step i if available.

Returning memoized results ensures each subproblem is solved once, guaranteeing linear time complexity.

Line 16 Core
return dp(len(cost))

Return the minimum cost to reach the top of the floor.

Calling dp with len(cost) represents reaching beyond the last step, yielding the final answer to the problem.

Line 11 Core
down_one = cost[i - 1] + dp(i - 1)

Calculate the cost of reaching step i by stepping from i-1.

This represents one possible path to step i, combining the cost of step i-1 with the minimum cost to reach i-1, reflecting the problem's recurrence.

Line 12 Core
down_two = cost[i - 2] + dp(i - 2)

Calculate the cost of reaching step i by stepping from i-2.

This alternative path considers jumping two steps, ensuring all valid sequences are evaluated for minimal cost.

Line 14 Core
return memo[i]

Return the memoized minimum cost for step i.

Returning the computed minimal cost completes the recursive step and propagates the result upward.

Line 5 Standard
def dp(i):

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

Encapsulating the recursive logic in a helper function allows clean management of the problem's state and memoization.

Test Your Understanding

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

Because memoization ensures each subproblem dp(i) is computed only once and reused thereafter, eliminating repeated recursive calls that would otherwise cause exponential growth.

Related Problems

Dynamic Programming pattern

Don't just read it. Drill it.

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

Unlock the Min Cost Climbing Stairs drill

or drill a free problem