Climbing Stairs
Count the ways to reach step n taking 1 or 2 steps at a time — a Fibonacci recurrence in disguise.
Overview
You stand at the bottom of a staircase with n steps and may climb 1 or 2 steps at a time. How many distinct sequences of moves reach the top? The answer for n = 1, 2, 3, 4, 5 is 1, 2, 3, 5, 8 — the Fibonacci numbers shifted by one.
It is the canonical "count the ways" DP: the state is "which step am I on", the transition is "where could I have come from", and the answer is a sum rather than a min/max. The same shape solves decoding a digit string, tiling a 2×n board, and reaching the end of an array with allowed jumps.
Intuition
A mental model before the formal terms.
To stand on step 4 you must have just arrived from step 3 (one step) or from step 2 (two steps). There is no other way in. So the number of ways to reach step 4 equals ways-to-reach-3 plus ways-to-reach-2 — and those two sets of paths are disjoint because their last move differs.
Concretely for n = 4: ways to reach 2 are {1+1, 2} (2 ways); ways to reach 3 are {1+1+1, 1+2, 2+1} (3 ways). So dp[4] = 3 + 2 = 5: 1111, 112, 121, 211, 22.
How it works
- State:
dp[i]= number of distinct ways to reach stepifrom step 0. - Transition:
dp[i] = dp[i-1] + dp[i-2]— the last move was either a 1-step fromi-1or a 2-step fromi-2. With a general step setS,dp[i] = Σ dp[i-s]fors ∈ S,s ≤ i. - Base case:
dp[0] = 1(one way to be at the bottom: do nothing) anddp[1] = 1. - Iteration order:
ifrom 2 ton, since every state depends on smaller ones. - Answer location:
dp[n]. - Space optimization: two rolling variables for the 1-or-2 case; for step set
Skeep a window ofmax(S)values.
Why it works
Optimal substructure (here, "counting substructure"): every path to step i decomposes uniquely into a path to some earlier step plus one final move. The number of paths to that earlier step is the same regardless of what comes after it, so it can be reused.
The transitions partition the set of paths to i by the last move, so summing the parts counts each path exactly once — no double counting, no omissions.
Correctness by induction on i; the base dp[0] = 1 is what makes dp[2] = dp[1] + dp[0] = 2 come out right (1+1 and 2).
Recognition
How to tell a problem wants this.
- The problem asks "how many ways" to reach a target position by a fixed set of moves.
- The input is a single number
n(or a 1D array) and moves are local (small jumps). - Brute-force enumeration of paths is exponential but the number of positions is linear.
Interactive visualization
Play, step, change the input. ← → and space work too.
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| 1 | 1 | · | · | · | · | · | · | · |
1dp[0] = 1; dp[1] = 12for i in 2 .. n:3 dp[i] = dp[i-1] + dp[i-2]4return dp[n]Pseudocode
1if n <= 1: return 12prev2 = 1 # dp[0]3prev1 = 1 # dp[1]4for i in 2..n:5 cur = prev1 + prev26 prev2, prev1 = prev1, cur7return prev1Implementations
1# dp[i] = number of ways to reach step i with 1- or 2-steps2def climb_stairs(n: int) -> int:31 · State table4 dp = [0] * (n + 1)52 · Base cases6 dp[0] = 1 # one way to stand still7 if n >= 1:8 dp[1] = 193 · Transition10 for i in range(2, n + 1):11 dp[i] = dp[i - 1] + dp[i - 2]124 · Answer13 return dp[n]14 15 16# Generalized: arbitrary step sizes, still O(n * len(steps))17def climb_stairs_steps(n: int, steps: list[int]) -> int:18 dp = [0] * (n + 1)19 dp[0] = 120 for i in range(1, n + 1):21 for s in steps:22 if s <= i:23 dp[i] += dp[i - s]24 return dp[n][0] * (n + 1)allocates the table;dp[0] = 1records the single empty path.dp[1] = 1is guarded byif n >= 1soclimb_stairs(0)does not raiseIndexError.range(2, n + 1)visits every state up to and includingn.- The generalized version loops over
stepsand addsdp[i - s]for each reachable predecessor.
O(n·k) for k step sizes. Exact for any n thanks to arbitrary-precision ints.
- Two rolling variables with tuple assignment
prev2, prev1 = prev1, prev1 + prev2give O(1) space. lru_cacheon a nested closure is the idiomatic top-down form, but recursion depth caps n around 1000.sum(dp[i - s] for s in steps if s <= i)is a readable one-liner for the generalized transition.
dp[0] = 0— the empty path must count as one way.- Using recursion with
lru_cachefor n = 10**5 and hittingRecursionError. - Forgetting
% MODwhen the statement asks for a modular count — Python will happily compute the huge exact value and time out.
- The counts grow like Fibonacci: C++
long longoverflows near n = 91, JS/TS doubles lose exactness near n = 77, Python stays exact. - Iterating the step set:
for (int s : steps)in C++,for (const s of steps)in JS/TS (for...ingives string keys),for s in stepsin Python. - Memoized recursion depth: Python is the most restrictive (~1000); the loop is the safe form everywhere.
Complexity
With k allowed step sizes: O(n·k) time, O(n) or O(max step) space.
Compare growth rates in the Complexity Explorer →When to use — and when not to
- "Number of ways" to reach a position with a small fixed set of local moves.
- As the stepping stone to House Robber (same state, max instead of sum) and to Grid DP (same idea in 2D — unique paths).
- When paths have costs and you want the cheapest — that is a min-plus recurrence (min cost climbing stairs), not counting.
- When the number of ways only needs to be checked for feasibility ("can you reach") — a boolean DP or a greedy reach scan (Jump Game) is simpler.
Alternatives
Common mistakes
- Setting
dp[0] = 0— thendp[2]comes out as 1 instead of 2. "Zero steps" is one valid (empty) path. - Confusing "distinct sequences of moves" with "distinct sets of steps landed on" — the former is what the recurrence counts.
- Forgetting the modulus when the problem asks for the count mod
10^9+7andnis large. - Applying the counting recurrence to a min-cost variant without swapping
+formin.
Interview patterns
- Climbing stairs with 1 or 2 steps (Fibonacci).
- Decode Ways:
dp[i] = dp[i-1]·[s[i] valid] + dp[i-2]·[s[i-1..i] valid]. - Min cost climbing stairs:
dp[i] = cost[i] + min(dp[i-1], dp[i-2]). - Unique paths in a grid: the 2D version where you arrive from above or from the left.
- Coin ChangeIntermediate