99.Climbing Stairs
Problem
You are climbing a staircase that has n steps. Each move you can climb either 1 step or 2 steps.
Return the number of distinct ways to reach the top (step n) starting from the ground (step 0). Two ways are different if the sequence of 1s and 2s is different.
Examples
Input: n = 2
Output: 2
Explanation: 1 + 1, or 2.Input: n = 3
Output: 3
Explanation: 1 + 1 + 1, 1 + 2, 2 + 1.Input: n = 5
Output: 8
Explanation: The answers for n = 1, 2, 3, 4, 5 are 1, 2, 3, 5, 8 โ the Fibonacci sequence.Constraints
1 <= n <= 45
๐ก Key Insight
The last move onto step i was either a 1-step from i - 1 or a 2-step from i - 2, and these two groups never overlap. So ways(i) = ways(i - 1) + ways(i - 2) with ways(0) = 1 and ways(1) = 1 โ exactly Fibonacci. Plain recursion recomputes the same ways(k) exponentially many times; caching or building bottom-up makes it linear, and since each value only needs the previous two, two variables are enough.
Recursion (Brute Force)
TimeO(2โฟ)SpaceO(n)Intuition
Stand on step i. To get here you must have come from i - 1 (one step) or i - 2 (two steps). Count the ways to reach each of those and add them. Recurse until you hit the base cases.
Recurrence: f(i) = f(i - 1) + f(i - 2), with f(0) = 1 (one way to stand still at the bottom) and f(1) = 1.
Algorithm
- If
n <= 1, return1. - Otherwise return
climbStairs(n - 1) + climbStairs(n - 2).
Visualization
The recursion tree for n = 5. Notice f(3) is computed twice and f(2) three times โ that duplicated work is what makes this exponential.
flowchart TD
A["f(5)"] --> B["f(4)"]
A --> C["f(3)"]
B --> D["f(3)"]
B --> E["f(2)"]
C --> F["f(2)"]
C --> G["f(1) = 1"]
D --> H["f(2)"]
D --> I["f(1) = 1"]
E --> J["f(1) = 1"]
E --> K["f(0) = 1"]
F --> L["f(1) = 1"]
F --> M["f(0) = 1"]Code
class Solution {
public int climbStairs(int n) {
// base cases: one way to be at step 0 or step 1
if (n <= 1) {
return 1;
}
// last move was either a 1-step or a 2-step
return climbStairs(n - 1) + climbStairs(n - 2);
}
}Complexity
- Time: O(2โฟ) โ each call branches into two (more precisely O(ฯโฟ) โ O(1.618โฟ));
n = 45will time out. - Space: O(n) โ maximum recursion depth.
Top-Down Memoization
TimeO(n)SpaceO(n)Intuition
f(i) depends only on i, so its answer never changes. Store it the first time it is computed in memo[i] and return the stored value on every later call. Each of the n + 1 subproblems is now solved exactly once.
Algorithm
- Create
memoof sizen + 1, filled with0(meaning "not computed" โ every real answer is โฅ 1). dfs(i): ifi <= 1return1; ifmemo[i] != 0return it.- Otherwise set
memo[i] = dfs(i - 1) + dfs(i - 2)and return it. - Return
dfs(n).
Dry Run
n = 5
dfs(5) โ dfs(4) โ dfs(3) โ dfs(2) โ dfs(1)=1, dfs(0)=1 โ memo[2]=2
dfs(3) = memo[2] + dfs(1) = 2 + 1 โ memo[3]=3
dfs(4) = memo[3] + memo[2] (cache hit) = 5 โ memo[4]=5
dfs(5) = memo[4] + memo[3] (cache hit) = 8Code
class Solution {
private int[] memo;
public int climbStairs(int n) {
memo = new int[n + 1];
return dfs(n);
}
private int dfs(int i) {
if (i <= 1) {
return 1;
}
if (memo[i] != 0) {
return memo[i]; // already solved
}
memo[i] = dfs(i - 1) + dfs(i - 2);
return memo[i];
}
}Complexity
- Time: O(n) โ each
ifrom2..nis computed once with O(1) work. - Space: O(n) โ
memoarray plus O(n) recursion stack.
Bottom-Up Tabulation
TimeO(n)SpaceO(n)Intuition
Instead of recursing from the top, fill the answers in increasing order of i. By the time we compute dp[i], both dp[i - 1] and dp[i - 2] are already known. No recursion, no stack.
Algorithm
- Create
dpof sizen + 1; setdp[0] = 1,dp[1] = 1. - For
ifrom2ton:dp[i] = dp[i - 1] + dp[i - 2]. - Return
dp[n].
Dry Run
n = 5:
| i | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| dp[i] | 1 | 1 | 2 | 3 | 5 | 8 |
dp[4] = dp[3] + dp[2] = 3 + 2 = 5, dp[5] = dp[4] + dp[3] = 5 + 3 = 8.
Code
class Solution {
public int climbStairs(int n) {
int[] dp = new int[n + 1];
dp[0] = 1; // standing at the ground
dp[1] = 1; // a single 1-step
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
}Complexity
- Time: O(n) โ one pass over the table.
- Space: O(n) โ the
dparray.
Space-Optimized DPOptimal
TimeO(n)SpaceO(1)Intuition
The tabulation loop only ever reads the last two entries. Keep them in two variables, prev2 = f(i - 2) and prev1 = f(i - 1), and slide them forward each step.
Algorithm
prev2 = 1(f(0)),prev1 = 1(f(1)).- For
ifrom2ton:curr = prev1 + prev2prev2 = prev1,prev1 = curr
- Return
prev1.
Dry Run
flowchart LR
A["start<br/>prev2=1, prev1=1"] -->|"i=2"| B["curr=2<br/>prev2=1, prev1=2"]
B -->|"i=3"| C["curr=3<br/>prev2=2, prev1=3"]
C -->|"i=4"| D["curr=5<br/>prev2=3, prev1=5"]
D -->|"i=5"| E["curr=8<br/>prev2=5, prev1=8"]
E --> F["return 8"]Code
class Solution {
public int climbStairs(int n) {
int prev2 = 1; // ways to reach step i - 2
int prev1 = 1; // ways to reach step i - 1
for (int i = 2; i <= n; i++) {
int curr = prev1 + prev2;
prev2 = prev1;
prev1 = curr;
}
return prev1; // for n = 1 the loop never runs and we return 1
}
}Complexity
- Time: O(n) โ single loop.
- Space: O(1) โ two integers.
Complexity Comparison
| Approach | Time | Space |
|---|---|---|
| Recursion (Brute Force) | O(2โฟ) | O(n) |
| Top-Down Memoization | O(n) | O(n) |
| Bottom-Up Tabulation | O(n) | O(n) |
| Space-Optimized DP | O(n) | O(1) |
Common Pitfalls
- Setting
f(0) = 0: there is exactly one way to "climb" zero steps (do nothing), andf(2) = f(1) + f(0)must equal2. - Allocating
new int[n + 1]and then writingdp[2]whenn = 1โ guard the base case or start the loop correctly. - Plain recursion without memoization times out long before
n = 45. - Overflow is not a concern here (
f(45) = 1,836,311,903fits inint), but the same pattern with largernneedslong.