Min Cost Climbing Stairs - The Discovery Journey
The Problem
You are given an integer array cost
where cost[i]
is the cost of ith step on a staircase. Once you pay the cost, you can either climb one or two steps.
You can either start from the step with index 0, or the step with index 1.
Return the minimum cost to reach the top of the floor.
Example 1:
Input: cost = [10,15,20]
Output: 15
Explanation: You will start at index 1.
- Pay 15 and climb two steps to reach the top.
The total cost is 15.
Example 2:
Input: cost = [1,100,1,1,1,100,1,1,100,1]
Output: 6
Explanation: You will start at index 0.
- Pay 1 and climb two steps to reach index 2.
- Pay 1 and climb two steps to reach index 4.
- Pay 1 and climb two steps to reach index 6.
- Pay 1 and climb one step to reach index 7.
- Pay 1 and climb two steps to reach index 9.
- Pay 1 and climb one step to reach the top.
The total cost is 6.
Layer 1: Designer's Brain - What Problem Are We Actually Solving?
The Confusion Point
You might confuse this with Climbing Stairs (LC 70): "How many ways can you reach step n?"
But this is Min Cost Climbing Stairs (LC 746): "What's the cheapest way to reach the top?"
Different problems, different DP states!
Problem 1: Climbing Stairs (LC 70)
- Question: "How many distinct ways to climb n stairs (1 or 2 steps at a time)?"
- State:
dp[i]
= number of ways to reach step i - Transition:
dp[i] = dp[i-1] + dp[i-2]
(sum of ways)
Problem 2: Min Cost Climbing Stairs (LC 746) ← This is YOUR problem
- Question: "Each step has a cost. What's the minimum cost to reach the top?"
- State:
dp[i]
= minimum cost to reach position i - Transition:
dp[i] = Math.min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])
Layer 2: The Messy Discovery Journey
Discovery Step 1: What does the input look like?
cost = [10, 15, 20]
↑ ↑ ↑
step0 step1 step2
- You can start at step 0 OR step 1 (for free!)
- You pay the cost when you LEAVE a step, not when you arrive
- The "top" is beyond the last step (position n, where n = cost.length)
Discovery Step 2: What does dp[i]
represent?
Designer's first attempt (WRONG):
"dp[i] = minimum cost to reach step i"
When you reach step i, you haven't paid cost[i]
yet!
The cost is paid when you LEAVE the step, not when you ARRIVE.
Designer's refined attempt (CORRECT):
"dp[i] = minimum cost to reach position i, having already paid all costs along the way"
This means when you're at position i, you've already paid for all the steps you took to get there.
Discovery Step 3: What's the recurrence relation?
Chronological thought process:
-
"To reach position i, where could I come from?"
- From step (i-1): cost to reach (i-1), then pay
cost[i-1]
to jump - From step (i-2): cost to reach (i-2), then pay
cost[i-2]
to jump
- From step (i-1): cost to reach (i-1), then pay
-
"So dp[i] should be..."
dp[i] = Math.min(
dp[i-1] + cost[i-1], // came from (i-1), paid to leave it
dp[i-2] + cost[i-2] // came from (i-2), paid to leave it
)
To reach position i:
- Option A: Be at step (i-1), pay
cost[i-1]
, jump 1 step → arrive at i - Option B: Be at step (i-2), pay
cost[i-2]
, jump 2 steps → arrive at i
Choose the cheaper option!
Discovery Step 4: What are the base cases?
Think physically:
- You can start at step 0 or step 1 for free
- So:
dp[0] = 0
anddp[1] = 0
You haven't paid anything yet—you're just standing there at your starting position, ready to jump.
You only pay when you LEAVE your starting position!
Layer 3: What Does "Top of the Floor" Mean?
Look at Example 1:
cost = [10, 15, 20]
indices: 0 1 2
Output is 15 - starting at index 1, pay 15, climb two steps.
"Wait, if I climb two steps from index 1, I'm at index 3... but there's no index 3!"
The Insight: The "top" is position n (one past the last step). It's like:
cost = [10, 15, 20, ???]
indices: 0 1 2 3 (TOP)The top is a virtual position you reach AFTER leaving the staircase.
Layer 4: Coder's Brain - Building the Solution
public int minCostClimbingStairs(int[] cost) {
int n = cost.length;
int[] dp = new int[n + 1]; // +1 because top is beyond last step
// BASE CASES: Start at step 0 or 1 for free
dp[0] = 0;
dp[1] = 0;
// RECURRENCE: For each position, choose cheaper path
for (int i = 2; i <= n; i++) {
dp[i] = Math.min(
dp[i-1] + cost[i-1], // pay cost[i-1] to jump from step (i-1)
dp[i-2] + cost[i-2] // pay cost[i-2] to jump from step (i-2)
);
}
// Return cost to reach the top (beyond last step)
return dp[n];
}
Data Structure Choices:
- Why array? We need to look back 2 steps, and we build sequentially
- Why n+1 size? We need
dp[n]
to represent the top - Why not optimize to O(1) space yet? Clarity first - you can optimize to two variables later
Layer 5: Edge Cases as Concept Validators
Edge Case 1: cost = [10, 15, 20]
Trace:
dp[0] = 0 (start here free)
dp[1] = 0 (start here free)
dp[2] = min(dp[1] + cost[1], dp[0] + cost[0])
= min(0 + 15, 0 + 10)
= 10
Now reaching the TOP (position 3):
dp[3] = min(dp[2] + cost[2], dp[1] + cost[1])
= min(10 + 20, 0 + 15)
= 15 ✓
Path: Start at step 1 → jump to step 3 (top), pay cost[1] = 15
"Why isn't the answer 10 (starting at step 0)?"
Answer: Because from step 0, you'd pay 10 to jump to step 1 or 2. Then you'd need to pay MORE to reach the top. Starting at step 1 and jumping directly is cheaper!
Edge Case 2: cost = [1,100,1,1,1,100,1,1,100,1]
Critical steps:
dp[0] = 0
dp[1] = 0
dp[2] = min(0 + 100, 0 + 1) = 1 // from step 0, pay 1
dp[3] = min(1 + 1, 0 + 100) = 2 // from step 2, pay 1
dp[4] = min(2 + 1, 1 + 1) = 2 // from step 2, pay 1 (skip step 3!)
At dp[4]
, even though we could come from dp[3]
, it's cheaper to come from dp[2]
:
dp[3] + cost[3] = 2 + 1 = 3
dp[2] + cost[2] = 1 + 1 = 2
✓
The DP automatically finds: "Sometimes jumping from further back is cheaper than taking the nearest step."
Your Mental Model: "Should I always take the cheapest step?"
Reality: No! It's about the cumulative path, not local decisions. The DP solves this optimally.
Layer 6: What You Were Missing
Common Wrong Attempt:
public int minCostClimbingStairs(int[] cost) {
int n = cost.length;
int[] dp = new int[n + 1];
dp[0] = cost[0]; // ❌ Paying cost too early
dp[1] = cost[0]; // ❌ This doesn't make sense
for (int i = 2; i <= n; i++) {
dp[i] = Math.min(dp[i-1], dp[i-2]); // ❌ Missing cost payment
}
return dp[n];
}
-
dp[0] = cost[0]
: You're saying "it costscost[0]
to reach step 0"- Reality: It costs NOTHING to START at step 0
- You only pay
cost[0]
when LEAVING step 0
-
dp[1] = cost[0]
: You're saying "it costscost[0]
to reach step 1"- Reality: It costs NOTHING to START at step 1
- If you reached step 1 FROM step 0, then yes, you'd have paid
cost[0]
, but you can start at step 1 directly!
-
Missing cost in recurrence: You forgot to add the cost when jumping
- Should be:
dp[i-1] + cost[i-1]
, not justdp[i-1]
- Should be:
Better Attempt (Closer):
dp[0] = 0; // ✓ Starting is free
dp[1] = 0; // ✓ Starting is free
for (int i = 2; i <= n; i++) {
dp[i] = Math.min(
dp[i-1] + cost[i-1], // ✓ CORRECT!
dp[i-2] + cost[i-2] // ✓ CORRECT!
);
}
Your recurrence relation Math.min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])
was perfect from the start! 🎯
Only the base cases were wrong.
Layer 7: The Complete Correct Solution
public int minCostClimbingStairs(int[] cost) {
int n = cost.length;
int[] dp = new int[n + 1]; // +1 for the top position
// Base cases: starting at step 0 or step 1 is free
dp[0] = 0;
dp[1] = 0;
// Fill dp array: for each position, choose the cheaper path
for (int i = 2; i <= n; i++) {
dp[i] = Math.min(
dp[i-1] + cost[i-1], // pay cost[i-1] to jump from step (i-1)
dp[i-2] + cost[i-2] // pay cost[i-2] to jump from step (i-2)
);
}
// Return cost to reach the top (position n)
return dp[n];
}
What Changed From Wrong Code:
Wrong | Correct | Why |
---|---|---|
dp[0] = cost[0] | dp[0] = 0 | Starting is free |
dp[1] = cost[0] | dp[1] = 0 | Starting is free |
i < n | i <= n | Need to calculate dp[n] (the top) |
Missing cost addition | + cost[i-1] | Pay to leave the step |
Layer 8: The Meta-Pattern
-
Clarify the state transition:
- "When do I pay the cost?" → When leaving, not entering
- "What does
dp[i]
represent?" → Cost to reach position i
-
Draw a small example by hand:
cost = [10, 15, 20]
↓ ↓ ↓
steps: 0 1 2 TOP -
Trace the physical action:
- "I'm at step 1, I pay 15, I jump to the TOP"
- This gives
dp[3] = dp[1] + cost[1] = 0 + 15 = 15
-
Verify base cases match problem constraints:
- "Can start at step 0 or 1" →
dp[0] = 0
,dp[1] = 0
- "Can start at step 0 or 1" →
The Core Insight
Cost is paid when LEAVING a step, not when ARRIVING
Therefore, to reach position i, you pay the cost of the step you're leaving (i-1 or i-2), not the cost of position i itself.
This is the conceptual hurdle - once you see that dp[i]
is cumulative cost to arrive at position i (not the cost of being at i), everything clicks.
Base Case Reasoning:
Starting positions are FREE (no cost to begin there). You only pay when you take your first step AWAY from your starting position.
Key Takeaways
-
✅ State Definition:
dp[i]
= minimum cost to reach position i (having paid all costs along the way) -
✅ Cost Timing: Pay
cost[i]
when LEAVING step i, not when arriving at it -
✅ Base Cases: Starting is free →
dp[0] = 0
,dp[1] = 0
-
✅ Top Position: The top is at position n (one beyond the last step)
-
✅ Recurrence: Choose the cheaper of two paths:
- Jump from (i-1):
dp[i-1] + cost[i-1]
- Jump from (i-2):
dp[i-2] + cost[i-2]
- Jump from (i-1):
Complexity: Time O(n), Space O(n) - can be optimized to O(1) space with two variables