DSA sheet · Recursion · Pattern 2

Non-Linear Recursion

This video teaches the second recursion pattern: a function that calls itself more than once. In the previous pattern (linear recursion, e.g. factorial) each call made only one new call, so the calls formed a straight line. Here each call makes two calls, so the calls spread out like a tree. The teacher shows why that tree is slow (the same small problems get solved again and again), and how saving answers (memoisation / DP) and then two variables fix it. She solves two problems with it: Fibonacci Number (LeetCode 509) and Climbing Stairs (LeetCode 70). This idea is the doorway to dynamic programming, so it matters a lot.

This is a concept video, so the order is:
① what the pattern is → ② how to recognise it → ③ the intuition → ④ every example problem she solves, each with its own full mini-section (question, constraints, base case, recursive case, code, line by line, recursion tree + stack dry run, complexity) → ⑤ a pattern template in Python → ⑥ remember

Part 0 · Recursion from scratch

1. A function that calls itself

Recursion means a function solves a problem by calling itself on a smaller version of the same problem. Example: to find 5! (5 factorial = 5×4×3×2×1) you can say "5! = 5 × 4!". Finding 4! is the same job, just smaller.

the smallest possible recursive function
def fact(n):
    if n == 0:              # base case: smallest problem, answer known
        return 1
    return n * fact(n - 1)  # recursive case: use the answer of a smaller problem

2. The base case: why it must exist

The base case is the input so small that we know the answer without calling again. It is the "stop" sign.

3. The recursive case and the "leap of faith"

The recursive case is the line that calls the function on a smaller input and builds the answer from it. When you write it, trust that the smaller call already returns the right answer. Don't trace it in your head all the way down. This trust is called the leap of faith. Your only jobs are: (1) the base case is correct, (2) every call moves closer to the base case, (3) you combine the smaller answer correctly.

4. The call stack: push on call, pop on return

Python keeps a call stack: a pile of "frames". Each frame is one unfinished call with its own variables.

fact(3): deepest moment
fact(3)fact(2)fact(1)fact(0) → 1
after fact(0) returns
fact(3)fact(2)fact(1) → 1×1 = 1
last frame
fact(3) → 3×2 = 6

Bottom of each picture = oldest call. Red = the frame running right now.

5. Work on the way down vs. work on the way back up

In this page, all the adding (f(n-1) + f(n-2)) happens on the way back up.

Part A · What non-linear recursion is

1The pattern

The teacher starts from the last video. In factorial, each call made one call: f(5) → f(4) → f(3) → f(2)… If you draw those calls, you get a straight line. That's why it was called linear recursion.

In non-linear recursion, one call makes two (or more) calls. For example f(n) calls f(n−1) and f(n−2). (It could also be f(n+1), or any other input. What matters is "more than one call".) Draw the calls now and each call splits into two branches, so the picture becomes a tree.

linear: one call each
f(5)
 │
f(4)
 │
f(3)
 │
f(2)  ...a line
non-linear: two calls each
          f(4)
         /    \
      f(3)    f(2)
      /  \    /  \
   f(2) f(1) f(1) f(0)
   /  \
 f(1) f(0)     ...a tree

2How to recognise it

3Intuition: the tree repeats itself

Look at the f(4) tree above. f(2) is solved twice, f(1) three times, f(0) twice. These are overlapping subproblems: the same small question shows up in different branches. The left branch already worked out f(2), so why does the right branch work it out again?

For f(4) the waste is small. For f(5), f(10), f(40) the tree explodes and the repeats multiply.

The big ideaWhen non-linear recursion repeats subproblems, save each answer the first time you compute it. When the same question comes again, look up the saved answer instead of recomputing. Saving answers like this is called memoisation, and it's the heart of DP (dynamic programming).
Doubt: is "overlapping subproblems" the only sign of DP?
→ No. The teacher mentions DP can also be spotted from things like "choices at each step". But overlapping subproblems is the sign that shows up naturally in non-linear recursion, and that's why these problems end up solved with DP.

4Speed rule: when is 2ⁿ still OK?

Each call makes 2 calls, and the tree is about n levels deep, so the number of calls grows like 2ⁿ. A rough rule: a judge allows around 10⁸ simple operations per second.

n2ⁿverdict
20≈ 10⁶fast
29–30≈ 5×10⁸ – 10⁹ (upper bound)still passes, but slow
45≈ 3.5×10¹³TLE (Time Limit Exceeded) for sure

So the teacher's rule of thumb: if n ≤ about 30, plain non-linear recursion can still be submitted; once n goes past 30, you must save answers.

Doubt: is it exactly 2ⁿ calls?
→ 2ⁿ is an upper bound (a ceiling) that's easy to remember. The right branch (n−2) is shorter than the left (n−1), so the real Fibonacci tree is a bit smaller: about 1.6ⁿ calls. For n = 30 it's 2,692,537 calls, not 10⁹. Either way it grows exponentially (multiplies with every +1 to n), which is the real problem.

5Space: recursion is not free

The teacher points out that recursion uses extra space even if you never create an array: every waiting call sits on the call stack. For the tree above, the stack is never taller than one root-to-leaf path, so the space is O(n) (the depth), not O(2ⁿ).


Part B · Fibonacci Number: plain recursion

LeetCode 509

1The question in simple words

The Fibonacci sequence starts with 0 and 1. Every later number is the sum of the two numbers before it.

term n01234567
F(n)011235813

Given n, return F(n), the n-th term (counting from term 0).

2What the constraints tell us

3Intuition: start from the simplest way you'd do it by hand

The teacher first forgets about recursion and asks: what's the most basic way? Make an array, put 0 and 1 in the first two slots (they can't be calculated, they're fixed), then fill each next slot as arr[i-1] + arr[i-2] (first previous + second previous). To get term 4, run the loop up to index 4 and return arr[4].

That loop shows the rule: term n needs the two previous terms. Recursion is just the same rule written as a function: "to get F(n), ask for F(n−1) and F(n−2) and add them".

4Building the base case and recursive case

Base cases: the terms that have no "two previous"

The formula needs two earlier terms, so it only works from term 2 onwards. Term 0 and term 1 have nothing (or only one thing) before them, so their answers must be fixed, exactly like the first two slots of the array:

Doubt 1: can these two ifs become one?
→ Yes, the teacher merges them: if n <= 1: return n. If n is 0 it returns 0, if n is 1 it returns 1. The answer equals n in both cases.

Recursive case: everything from n = 2 up

return fib(n - 1) + fib(n - 2). Check n = 2: it calls fib(1) → 1 and fib(0) → 0, adds them → 1 ✓. This one line makes two calls, which is what makes it non-linear.

Doubt 2: why can't I tell it's non-linear from this tiny example?
→ For n = 2 the calls go straight to base cases, so nothing looks special. You have to draw the calls for a bigger n (like 4) to see the tree and the repeats. That's what the dry run below does.

5Approach steps

  1. If n ≤ 1, return n (fixed first two terms).
  2. Otherwise, ask the same function for term n−1 and term n−2.
  3. Return their sum.

6Code (Python)

Fibonacci: plain recursion
class Solution:
    def fib(self, n: int) -> int:
        if n <= 1:                                  # base case: F(0)=0, F(1)=1
            return n
        return self.fib(n - 1) + self.fib(n - 2)    # two calls -> non-linear

7Code line by line

linewhat it means
if n <= 1: return nBase case. Terms 0 and 1 are fixed. This also stops every branch of the tree, because every branch keeps subtracting until it hits 1 or 0.
self.fib(n - 1)Leap of faith: trust it returns the first previous term. Python pauses here and finishes that whole left branch first.
+ self.fib(n - 2)Then the second previous term (the right branch).
return …The adding happens on the way back up, after both calls return.

8Dry run: fib(4)

Each node shows call = what it returns. Red = a call that repeats work already done somewhere else in the tree.

                      fib(4)=3
                   /            \
            fib(3)=2             fib(2)=1   ← repeat
            /      \             /      \
      fib(2)=1    fib(1)=1   fib(1)=1  fib(0)=0
      /      \
 fib(1)=1  fib(0)=0
  1. fib(4) starts. 4 > 1, so it calls fib(3) first. fib(4) waits.
  2. fib(3) calls fib(2). fib(3) waits.
  3. fib(2) calls fib(1).
  4. fib(1) is a base case → returns 1. Its frame is popped.
  5. Back in fib(2), now it calls fib(0) → returns 0.
  6. fib(2) = 1 + 0 = 1, returned to fib(3).
  7. fib(3) now calls its right side fib(1) → 1. fib(3) = 1 + 1 = 2, returned to fib(4).
  8. fib(4) now calls its right side fib(2). We already found fib(2) = 1 in step 6, but plain recursion has no memory, so it does it all again: fib(1) → 1, fib(0) → 0, fib(2) = 1.
  9. fib(4) = 2 + 1 = 3. The stack is empty. Answer 3 ✓
step 4 (deepest)
fib(4)fib(3)fib(2)fib(1) → 1
step 7
fib(4)fib(3) → 2
step 8 (the repeat)
fib(4)fib(2)fib(0) → 0

The stack is never taller than 4 frames (= n), even though 9 calls happen in total.

9Complexity & remember

Counting calls. Let calls(n) = how many calls fib(n) makes in total. calls(0) = calls(1) = 1, and calls(n) = calls(n−1) + calls(n−2) + 1.

n0123451030
calls11359151772,692,537
Rememberif n <= 1: return n then return fib(n-1) + fib(n-2). Two calls → a tree → repeated subproblems → 2ⁿ time.

Part C · Fibonacci Number: saving answers

1The idea

The plain recursion repeats work. The fix the teacher gives: save every answer once, then reuse it. She describes it with an array: slot 0 = 0, slot 1 = 1, and from slot 2 onward, each slot = sum of the two before it. The same "save it" idea can also be added inside the recursion (top-down memoisation). Both are shown below because both are DP.

top-down (memoised recursion)bottom-up (array / table)
directionstart at n, go down to the base cases, save answers on the way back upstart at 0 and 1, fill upward to n with a loop
uses recursion?yesno
extra spacememo array + call stackthe array only

2Constraints, once more

Nothing new: n from 0 to 30. Watch the n = 0 case in the array version: an array of size n+1 = 1 has only slot 0, so writing to slot 1 would crash. We return early for n ≤ 1.

3Approach steps (top-down)

  1. Make memo of size n+1, filled with −1 ("not computed yet").
  2. In the recursive function: base case n ≤ 1 → return n.
  3. If memo[n] is not −1, the answer is already known → return it (no new calls).
  4. Otherwise compute f(n-1) + f(n-2), store it in memo[n] first, then return it.

4Code (Python)

Fibonacci: memoised recursion (top-down DP)
class Solution:
    def fib(self, n: int) -> int:
        memo = [-1] * (n + 1)              # -1 means "not computed yet"

        def f(k):
            if k <= 1:                     # base case
                return k
            if memo[k] != -1:              # already solved? reuse it
                return memo[k]
            memo[k] = f(k - 1) + f(k - 2)  # solve once, save
            return memo[k]

        return f(n)
Fibonacci: array (bottom-up DP), the version the teacher describes
class Solution:
    def fib(self, n: int) -> int:
        if n <= 1:                         # also protects dp[1] when n == 0
            return n
        dp = [0] * (n + 1)
        dp[0], dp[1] = 0, 1                # the two fixed terms
        for i in range(2, n + 1):
            dp[i] = dp[i - 1] + dp[i - 2]  # first previous + second previous
        return dp[n]

5Code line by line

linewhat it means
memo = [-1] * (n + 1)One slot per term 0..n. −1 is safe as "empty" because no Fibonacci number is negative.
if memo[k] != -1: return memo[k]The whole speed-up. A repeated question is answered from the notebook in O(1), so its subtree is never drawn.
memo[k] = f(k-1) + f(k-2) return memo[k]Save before returning, so every later caller finds it.
dp[0], dp[1] = 0, 1Bottom-up: the fixed first two terms, the same as the base cases.
for i in range(2, n + 1): dp[i] = dp[i-1] + dp[i-2]Fill each term from the two already-filled terms before it. When we reach i, both are ready.
return dp[n]The n-th slot holds the answer.

6Dry run: fib(4) with memo

                 f(4)=3  saves memo[4]
                /        \
   saves memo[3] f(3)=2    f(2)=1  ← memo hit, no children
              /     \
 saves memo[2] f(2)=1  f(1)=1
           /    \
       f(1)=1  f(0)=0
  1. f(4) → f(3) → f(2) → f(1) = 1, f(0) = 0 → memo[2] = 1.
  2. Back in f(3): f(1) = 1 → memo[3] = 1 + 1 = 2.
  3. Back in f(4): it asks for f(2). memo[2] is 1, already saved → return immediately. The red branch from Part B is gone.
  4. memo[4] = 2 + 1 = 3. Answer 3 ✓. Total calls: 7 instead of 9 (for n = 30: 59 instead of 2.7 million).
deepest
f(4)f(3)f(2)f(1) → 1
step 3: memo hit
f(4)f(2) → memo 1

Bottom-up table for n = 5:

i012345
dp[i]010+1 = 11+1 = 21+2 = 32+3 = 5

7Complexity & remember

RememberMemoisation = "check the notebook first, solve only if empty, write the answer in the notebook before returning". It turns 2ⁿ into n.

Part D · Fibonacci Number: two variables, O(1) space

1The question again, and what we want now

Same question. The teacher's next point: the array and the recursion both use extra space. Can we avoid it?

2Intuition: we only ever look two slots back

To fill dp[i] we only read dp[i−1] and dp[i−2]. Everything older is never read again. So instead of a whole array, keep just two variables:

She shows it with the terms 1, 2, 3, 4: to make 3 you need prev1 = 2 and prev0 = 1. For the next term (4), the pair slides one step right: the old prev1 (2) becomes the new prev0, and the term just made (3) becomes the new prev1.

3Building the update: why a temp variable?

Each loop step needs two updates: new prev0 = old prev1, and new prev1 = prev0 + prev1.

Doubt: why not just write prev1 = prev0 + prev1 and then prev0 = prev1?
→ The first line overwrites prev1 with the new sum. The second line then copies that new sum into prev0, and the old prev1 is lost. Example with prev0 = 1, prev1 = 2: after line 1, prev1 = 3. After line 2, prev0 = 3 too. Now both are 3, wrong. We wanted prev0 = 2, prev1 = 3.
So the teacher first puts the sum in temp, then moves prev1 into prev0, then puts temp into prev1. Nothing gets lost.
Python shortcut: prev0, prev1 = prev1, prev0 + prev1 works too, because Python works out the whole right side before assigning anything.

4Approach steps

  1. If n ≤ 1, return n.
  2. prev0 = 0, prev1 = 1.
  3. For i from 2 to n: temp = prev0 + prev1; prev0 = prev1; prev1 = temp.
  4. After the loop, prev1 holds term n → return it.

5Code (Python)

Fibonacci: two variables (fastest, O(1) space)
class Solution:
    def fib(self, n: int) -> int:
        if n <= 1:
            return n
        prev0, prev1 = 0, 1              # term 0 and term 1
        for i in range(2, n + 1):
            temp = prev0 + prev1         # the current term
            prev0 = prev1                # slide: first previous becomes second previous
            prev1 = temp                 # the current term becomes first previous
        return prev1

6Code line by line

linewhat it means
if n <= 1: return nSame base case as the recursion. Without it, n = 0 would wrongly return prev1 = 1.
prev0, prev1 = 0, 1The two fixed terms.
for i in range(2, n + 1):Compute terms 2, 3, …, n (inclusive, hence n+1).
temp = prev0 + prev1Term i, kept safe in temp.
prev0 = prev1 prev1 = tempSlide the window one step right, in this order.
return prev1After the last step, prev1 is term n.

7Dry run: n = 5 (hand table)

itemp = prev0 + prev1prev0prev1
start–01
20 + 1 = 111
31 + 1 = 212
41 + 2 = 323
52 + 3 = 535 → return

8Complexity

9Remember

Fibonacci ladderrecursion O(2ⁿ) / O(n) → save answers O(n) / O(n) → two variables O(n) / O(1). Always sum into temp before sliding.

Part E · Climbing Stairs: building the logic + recursion

LeetCode 70

1The question in simple words

A staircase has n steps. From where you stand, each move is either 1 step or 2 steps. In how many different ways can you reach the top (step n)?

Careful: it doesn't ask for the number of jumps. It asks for the number of different sequences of jumps. "1 then 2" and "2 then 1" are two different ways.

The teacher says up front: this is Fibonacci in disguise, only the base cases change. Let's discover that the way she does.

2What the constraints tell us

3Intuition: break it into small cases

We don't know the ways to reach step 10 directly. So the teacher starts from the tiniest staircases and counts by hand.

4Building the logic from examples

nall the wayscount
111
21+1 · 22
31+1+1 · 1+2 · 2+13
41+1+1+1 · 1+1+2 · 1+2+1 · 2+1+1 · 2+25
Doubt 1: 1, 2, 3… is the answer just n?
→ No. n = 4 gives 5, which breaks that guess. Look again: 3 = 2 + 1 and 5 = 3 + 2. Each answer is the sum of the previous two answers, just like Fibonacci. The only difference is the start: Fibonacci starts at term 0 with 0, 1, while here the sequence starts at n = 1 with 1, 2.
Doubt 2: why is it "previous two" here? (the reason behind the pattern)
→ Look at the last jump onto step n. It is either a 1-jump (from step n−1) or a 2-jump (from step n−2). Every way to reach n−1, plus one final 1-jump, is a way to reach n. Every way to reach n−2, plus one final 2-jump, is also a way. These two groups don't overlap and together cover everything. So ways(n) = ways(n−1) + ways(n−2).

The base cases change

In Fibonacci we wrote n <= 1 because terms 0 and 1 were fixed. Here the problem starts at 1, and the fixed answers are ways(1) = 1 and ways(2) = 2. Both are "answer = n", so the base case becomes if n <= 2: return n. That's the only change from Fibonacci.

Doubt 3: why must n = 2 be a base case? Can't the formula do it?
→ The formula would give ways(2) = ways(1) + ways(0), and ways(0) is outside the problem (n ≥ 1). It's simpler and safer to fix 1 and 2, exactly as Fibonacci fixed 0 and 1.

5Approach steps

  1. If n ≤ 2, return n.
  2. Otherwise return ways(n−1) + ways(n−2).

6Code (Python)

Climbing Stairs: plain recursion (TLE for big n)
class Solution:
    def climbStairs(self, n: int) -> int:
        if n <= 2:                                          # ways(1)=1, ways(2)=2
            return n
        return self.climbStairs(n - 1) + self.climbStairs(n - 2)

7Code line by line

linewhat it means
if n <= 2: return nThe two fixed answers. Every branch ends at 1 or 2.
self.climbStairs(n - 1)All ways whose last jump is 1.
+ self.climbStairs(n - 2)All ways whose last jump is 2.

8Dry run: n = 5

                    cs(5)=8
                 /           \
           cs(4)=5           cs(3)=3   ← repeat
           /     \            /     \
      cs(3)=3   cs(2)=2  cs(2)=2  cs(1)=1
      /    \
 cs(2)=2  cs(1)=1
  1. cs(5) → cs(4) → cs(3) → cs(2) = 2 (base), then cs(1) = 1 (base).
  2. cs(3) = 2 + 1 = 3, returned to cs(4).
  3. cs(4) calls cs(2) = 2 → cs(4) = 3 + 2 = 5, returned to cs(5).
  4. cs(5) calls cs(3). cs(3) was already solved in step 2, but it's solved again: cs(2) = 2, cs(1) = 1 → 3.
  5. cs(5) = 5 + 3 = 8 ✓
step 1 (deepest)
cs(5)cs(4)cs(3)cs(2) → 2
step 4 (the repeat)
cs(5)cs(3)cs(1) → 1

9Complexity & remember

RememberClimbing Stairs = Fibonacci with base n <= 2: return n. Plain recursion is correct but TLEs at n = 45.

Part F · Climbing Stairs: array and two variables

1What changes from Fibonacci

The teacher copies her Fibonacci solutions and changes only the starting values:

FibonacciClimbing Stairs
base casen <= 1n <= 2
first two fixed valuesF(0) = 0, F(1) = 1ways(1) = 1, ways(2) = 2
loop starts at23
rulesame: current = first previous + second previous

2Array version (bottom-up DP)

Make an array of size n+1, fix slots 1 and 2, then fill from slot 3 to slot n with the sum of the two slots before. Return slot n.

Climbing Stairs: array DP
class Solution:
    def climbStairs(self, n: int) -> int:
        if n <= 2:                          # also stops dp[2] crashing when n == 1
            return n
        dp = [0] * (n + 1)
        dp[1], dp[2] = 1, 2
        for i in range(3, n + 1):
            dp[i] = dp[i - 1] + dp[i - 2]
        return dp[n]
Doubt: why the early return before making the array?
→ If n = 1, the array has size 2 (slots 0 and 1). Writing dp[2] would give an IndexError. Returning early for n ≤ 2 avoids that.

3Memoised recursion (top-down), for completeness

Climbing Stairs: memoised recursion
class Solution:
    def climbStairs(self, n: int) -> int:
        memo = {}

        def ways(k):
            if k <= 2:
                return k
            if k in memo:                   # solved before -> reuse
                return memo[k]
            memo[k] = ways(k - 1) + ways(k - 2)
            return memo[k]

        return ways(n)
memo tree for n = 5:   ws(5)=8
                      /       \
                 ws(4)=5     ws(3) memo hit = 3
                 /     \
            ws(3)=3   ws(2)=2
            /    \
       ws(2)=2  ws(1)=1          7 calls instead of 9

4Two-variable version

Climbing Stairs: two variables, O(1) space
class Solution:
    def climbStairs(self, n: int) -> int:
        if n <= 2:
            return n
        prev0, prev1 = 1, 2                 # ways(1), ways(2)
        for i in range(3, n + 1):
            temp = prev0 + prev1
            prev0 = prev1
            prev1 = temp
        return prev1

5Code line by line

linewhat it means
dp[1], dp[2] = 1, 2The two fixed answers. (dp[0] stays unused.)
for i in range(3, n + 1):Fill step 3 up to step n.
prev0, prev1 = 1, 2Same two answers, kept in variables instead of an array.
temp = prev0 + prev1 …Same sliding window as Fibonacci: sum into temp, slide, store.

6Dry run: n = 5

itempprev0prev1dp array so far
start–12[_, 1, 2, _, _, _]
31+2 = 323[_, 1, 2, 3, _, _]
42+3 = 535[_, 1, 2, 3, 5, _]
53+5 = 858 → return[_, 1, 2, 3, 5, 8]

7Complexity

versiontimespacen = 45?
plain recursionO(2ⁿ)O(n) stackTLE
memo / arrayO(n)O(n)passes
two variablesO(n)O(1)passes, fastest

8Why the teacher calls the plain one "the worst"

It loses on both counts: exponential time, and it still uses O(n) space for its hidden call stack. The array fixes time but keeps O(n) space. Two variables fix both.

9Remember

RememberClimbing Stairs: base 1 → 1, 2 → 2; loop from 3; prev0, prev1 = 1, 2. Everything else is Fibonacci.

Part G · The pattern template

Any "answer depends on the previous two answers" problem follows this ladder. Write the plain recursion first to get the logic right, then climb.

non-linear recursion → memo → two variables
# 1) plain non-linear recursion: correct, but O(2^n)
def solve(n):
    if n is small:                         # fixed answers (base cases)
        return known_answer(n)
    return solve(n - 1) + solve(n - 2)     # two calls -> tree

# 2) memoised: O(n) time, O(n) space
def solve(n, memo):
    if n is small:
        return known_answer(n)
    if n in memo:                          # check the notebook first
        return memo[n]
    memo[n] = solve(n - 1, memo) + solve(n - 2, memo)
    return memo[n]                         # save before returning

# 3) two variables: O(n) time, O(1) space
def solve(n):
    if n is small:
        return known_answer(n)
    prev0, prev1 = known_answer(first), known_answer(second)
    for i in range(second + 1, n + 1):
        temp = prev0 + prev1
        prev0 = prev1
        prev1 = temp
    return prev1

This is a sketch (the "n is small" lines are placeholders), not runnable code.

Recognise itTwo or more self-calls → draw the tree → if the same arguments appear in different branches, you have overlapping subproblems → save answers. If n ≤ ~30, plain recursion may still pass; past that, it won't.

Part H · Revision page

Linear recursionNon-linear recursion
self-calls per call12 or more
shape of the callsa linea tree
examplefactorial: f(n) = n · f(n−1)Fibonacci: f(n) = f(n−1) + f(n−2)
repeated subproblems?nooften yes → DP
timeO(n)O(2ⁿ) without saving answers
stack spaceO(n)O(depth) = O(n)
Fibonacci (LC 509)Climbing Stairs (LC 70)
n range0 … 301 … 45
base casen <= 1 → nn <= 2 → n
rulef(n) = f(n−1) + f(n−2)
plain recursionpasses (n ≤ 30)TLE (n = 45)
besttwo variables: O(n) time, O(1) space
If you remember only 5 lines 1. Non-linear = the function calls itself 2+ times → the calls form a tree.
2. Draw the tree: the same arguments in different branches = overlapping subproblems.
3. 2ⁿ calls is fine up to n ≈ 30, TLE beyond.
4. Save answers (memo / array) → O(n). Only need the last two → two variables → O(1) space.
5. Climbing Stairs is Fibonacci starting from 1, 2 instead of 0, 1.
Mistakes to avoid ✗ forgetting n = 0 in Fibonacci (or n = 1 in the array version: index out of range)
✗ prev1 = prev0 + prev1 then prev0 = prev1 (old value lost: use temp)
✗ in memo: returning the sum without saving it first
✗ using Fibonacci's base n <= 1 for Climbing Stairs (gives 1 for n = 2)
✗ loop range(2, n) instead of range(2, n + 1) (stops one term early)
test it yourself (paste under any of the solutions above)
s = Solution()
# for a Fibonacci solution:
print([s.fib(n) for n in range(8)])          # [0, 1, 1, 2, 3, 5, 8, 13]
print(s.fib(30))                             # 832040
# for a Climbing Stairs solution:
print([s.climbStairs(n) for n in range(1, 6)])   # [1, 2, 3, 5, 8]
print(s.climbStairs(45))                     # 1836311903 (skip for the plain recursion!)

Based on this video: Non-Linear Recursion | Fibonacci Number & Climbing Stairs