AI 230 · Notes 03
Week 3 · Sep 15 & 17, 2026
Notes 03 · Week 3 · Sessions 4–5

Recursion, Recurrence Relations, and the Master Theorem

AI 230 · Introduction to Algorithms Prof. Yair Chaya Long Island University, Brooklyn Tuesday & Thursday, September 15 & 17, 2026 · 4:00–5:50 PM

Two threads meet this week. In Notes 01 you proved a recursive procedure correct by induction; in Notes 02 you priced loops by counting. This week the code starts calling itself in earnest, and pricing it needs a new instrument: the recurrence relation — an equation a recursive algorithm writes about its own cost. We will learn to extract that equation from code, solve it three ways by hand, and then meet the Master Theorem, which solves the most common family on sight. All of it is preparation for next week, when divide and conquer stops being an exercise and becomes merge sort and quicksort.

Three live demonstrations · marked ● in the contents

A call-stack explorer that runs naive Fibonacci one call at a time and counts what it wastes, a recursion-tree builder where you set a, b, and f(n) and watch which levels of the tree carry the cost, and a Master Theorem checker that names the case and the answer while showing the comparison that decides it. Every number is computed live. Drive them while you read.

Session planHow we will spend the two sessions

Tuesday, September 15 — Session 4

0:00
Answers to the Notes 02 classification exercises
0:10
Thinking recursively — the three obligations, the leap of faith (§1)
0:30
The call stack — Fibonacci explorer (§2)
1:00
From code to recurrence (§3)
1:25
Solving by unrolling — three worked solutions (§4)

Thursday, September 17 — Session 5

0:00
Recursion trees — tree explorer (§5)
0:40
The substitution method, briefly (§6)
1:00
The Master Theorem — case checker (§7)
1:30
When the theorem is silent (§8)
1:45
Project 1 stand-up: where everyone is (§9)

Section 1Thinking recursively

Definition

A recursive algorithm solves a problem by solving one or more strictly smaller instances of the same problem and combining their answers. An instance small enough to answer directly is a base case.

Recursion works whenever the problem is self-similar — when an instance contains smaller instances of itself. Much of computing has this shape once you look for it. A list is either empty or one element in front of a smaller list. A sorted array splits at its midpoint into two sorted arrays. A directory contains files and smaller directory trees. When the data is self-similar, a procedure that mirrors the self-similarity is often the shortest correct thing you can write.

You have already met the reasoning that makes recursion trustworthy. Notes 01 §6.2 stated the obligations for a recursive procedure, and they are worth restating now as a designer's checklist rather than a prover's:

  1. Base case. Some instances are answered directly, with no recursive call — and they are answered correctly.
  2. Progress. Every recursive call is on a strictly smaller instance, so the base case is always reached.
  3. Combination. Assuming the recursive calls return correct answers, the way you combine them produces a correct answer for the whole.

Obligation 3 contains the skill that separates people who can write recursion from people who trace it miserably. When you design or read a recursive procedure, you do not follow the call downward — you take the recursive leap of faith: assume the call on the smaller instance simply works, exactly as its specification says, and check only that the combination is right. This is not wishful thinking; it is the inductive hypothesis from Notes 01, worn as a design habit. Tracing the whole call tree is what the machine does. Trusting the specification of the smaller call is how humans design.

def factorial(n):
    """Return n! = n · (n−1) · … · 1.

    Precondition:  n is an integer, n >= 0.
    Postcondition: result == n!  (with 0! == 1).
    """
    if n == 0:                      # base case, answered directly
        return 1
    return n * factorial(n - 1)     # smaller instance + combination

def binary_search_rec(A, target, lo, hi):
    """Return an index of target in A[lo..hi], or None if absent.

    Precondition:  A is sorted; 0 <= lo and hi <= len(A) - 1.
    Postcondition: result is a valid index of target in A[lo..hi],
                   or None if target does not occur there.
    """
    if lo > hi:                     # base case: empty range
        return None
    mid = (lo + hi) // 2
    if A[mid] == target:
        return mid
    elif A[mid] < target:
        return binary_search_rec(A, target, mid + 1, hi)
    else:
        return binary_search_rec(A, target, lo, mid - 1)

Read binary_search_rec with the leap of faith and it becomes three lines of thought. Base: an empty range cannot contain the target, so None is right. Progress: each call's range is at least one element smaller. Combination: the midpoint comparison, plus the sortedness precondition, guarantees the target can only live in the half we recursed into — so the child's answer, whatever it is, is the answer. No trace required. That is the entire correctness argument, and you wrote its iterative twin's invariant last week.

Check yourself — Name the base case, the progress argument, and the combination step of binary_search_rec. Which of the three uses the precondition that A is sorted?

Base: lo > hi, the empty range — return None. Progress: the recursive range shrinks by at least one every call (it excludes mid), so it reaches empty. Combination: return the child's answer unchanged. It is the combination that spends the sortedness precondition: discarding half the range is only sound because sortedness proves the target cannot be there. Break the precondition and base and progress still hold — the procedure still terminates, quickly and confidently, with an answer that may simply be wrong. Preconditions are load-bearing; Notes 01 said so, and here is the second example.

Section 2The call stack

Before we price recursion we should be honest about how the machine runs it, because the mechanism has costs of its own. Every function call pushes a frame onto the call stack: the parameters, the local variables, and where to resume when the call returns. A recursive procedure with many live calls holds many frames at once — the chain from the outermost call down to wherever work currently is. Two consequences follow immediately.

Now the demonstration. Naive Fibonacci is the canonical example of recursion's other hidden cost — not depth, but count:

def fib(n):
    """Return the n-th Fibonacci number.

    Precondition:  n is an integer, n >= 0.
    Postcondition: result == F(n), where F(0)=0, F(1)=1,
                   and F(n) = F(n−1) + F(n−2).
    """
    if n < 2:                        # two base cases
        return n
    return fib(n - 1) + fib(n - 2)

All three obligations hold — this procedure is perfectly correct. Step it below and watch what correctness fails to promise. The stack grows down the leftmost chain, unwinds, regrows; and the counter on the right records something alarming: the same subproblems being solved again, and again, and again.

Demonstration 1Fibonacci explorer — the call stack, and the waste it hides
Compute
Call stack — outermost at the top
Calls per argument — highlighted when computed more than once
no calls yet
Total calls
0
Stack depth · now / max
0 / 0
Result
Press Step to push the first call, or Run to watch the whole computation.

Let the run finish and read the chips. For fib(6): twenty-five calls to produce the number 8 — fib(2) computed five times, fib(1) computed eight times, each recomputation oblivious to the others. The call count obeys C(n) = C(n−1) + C(n−2) + 1, which grows like the Fibonacci numbers themselves: exponentially, Θ(φⁿ) with φ ≈ 1.618. Move the selector to fib(8) and watch the total climb; every +2 on the argument roughly e-fold-ish multiplies the work. Meanwhile the stack depth — the other statistic — never exceeds n. Exponential time, linear space.

Depth and count are different resources

The stack holds only the current chain of unfinished calls, not every call ever made. fib(n) makes Θ(φⁿ) calls but holds at most n frames at once: exponential time, linear space. Keep the two ledgers separate in your analyses — and note for Week 9: the waste here is recomputation of identical subproblems, and the cure (remember each answer the first time — memoization) turns this exact function linear. One of the four project strategies, dynamic programming, is that observation grown up.

Check yourself — In the run of fib(6), how many times is fib(1) called? Predict before checking the chips, and find the pattern.

Eight times — and 8 is exactly fib(6) itself. No coincidence: the only way the procedure ever produces value is base cases returning 0 or 1, so the final answer equals the number of fib(1) leaves (each contributing 1) in the call tree. The answer 8 is literally assembled out of eight separate journeys to the same base case. An algorithm whose output is n therefore makes at least n base-case calls this way — a lower-bound argument, in the spirit of Ω from Notes 02, made by looking at where value comes from.

Section 3From code to recurrence

Loops let us count directly; recursion does not, because the cost of a call depends on the cost of smaller calls — which is circular. The instrument that tames the circularity embraces it:

Definition

A recurrence relation defines a function of n in terms of its own values on smaller arguments, plus a base case. When T(n) is the running time of a recursive algorithm on inputs of size n, the recurrence is the algorithm's cost equation — the exact shape of the code, with the details forgotten and the structure kept.

Extracting the recurrence from code is a four-step recipe, and it is nearly mechanical:

  1. Declare the variable. T(n) = worst-case cost on instances of size n — and say what "size" means for this problem.
  2. Price the local work: everything the body does besides the recursive calls — the splitting, the testing, the combining.
  3. Add the recursive calls at their sizes: each call on an instance of size m contributes T(m).
  4. Record the base case — almost always T(constant) = Θ(1).

Applied to binary_search_rec: local work is Θ(1) — one midpoint, one comparison — and there is one recursive call on about half the range. Applied to factorial: Θ(1) local work, one call on n−1. For the third example, here is the silhouette of an algorithm you will meet properly on Tuesday — for now, only its cost structure matters:

SORT(A) // n = length of A if n ≤ 1: return A // base case L ← SORT(left half of A) // T(n/2) R ← SORT(right half of A) // T(n/2) return MERGE(L, R) // Θ(n) — one pass over both halves

And one more with a different personality — the Tower of Hanoi, moving n disks legally between three pegs:

def hanoi(n, src, dst, spare):
    """Print the moves that shift n disks from src to dst, legally.

    Precondition:  n >= 0; disks on src are stacked small-on-large.
    Postcondition: the printed moves transfer the n disks to dst
                   without ever placing a larger disk on a smaller.
    """
    if n == 0:
        return
    hanoi(n - 1, src, spare, dst)      # T(n−1)
    print(src, "→", dst)               # Θ(1)
    hanoi(n - 1, spare, dst, src)      # T(n−1)

Collect all five. Each row is nothing but the recipe applied once — and notice how much of the algorithm survives into its equation:

AlgorithmShape of the codeRecurrence
Factorialone call on n−1, constant glueT(n) = T(n−1) + c
Binary searchone call on n/2, constant glueT(n) = T(n/2) + c
The sort silhouettetwo calls on n/2, linear glueT(n) = 2T(n/2) + cn
Tower of Hanoitwo calls on n−1, constant glueT(n) = 2T(n−1) + c
Naive Fibonaccicalls on n−1 and n−2, constant glueT(n) = T(n−1) + T(n−2) + c
Sloppiness that is safe — and sloppiness that is not

Three shortcuts are standard and provably harmless to the Θ answer: ignore floors and ceilings (treat n/2 as exact, or assume n is a power of 2); write any constant local work as a single c; let the base case be "T(small) = Θ(1)" without specifying which small. What you may not blur is structure: how many recursive calls, on what sizes, with how much glue. Those three numbers are the algorithm; everything else is weather. The whole of §7 is a theorem about exactly those three numbers.

Section 4Solving by unrolling

A recurrence is a compressed sum; the most elementary way to solve one is to decompress it. Substitute the recurrence into itself until the pattern shows, run the pattern down to the base case, and add up everything shed along the way. Three unrollings below cover the three most important personalities — read each until the "pattern" line feels inevitable, because on Thursday you will be asked to produce lines like it unprompted.

One call, shrinking by subtraction

T(n) = T(n−1) + c = [T(n−2) + c] + c = T(n−2) + 2c = [T(n−3) + c] + 2c = T(n−3) + 3c … after k steps: T(n−k) + k·c base case at k = n: T(0) + n·c → Θ(n)

Factorial, and every recursion that peels one element per call with constant glue, is a loop in disguise: linear. (With linear glue instead — T(n) = T(n−1) + cn — the shed terms form the triangular sum from Notes 02 §4 and the answer is Θ(n²); that is the check question below.)

One call, shrinking by division

T(n) = T(n/2) + c = [T(n/4) + c] + c = T(n/4) + 2c = T(n/8) + 3c … after k steps: T(n/2ᵏ) + k·c base case at n/2ᵏ = 1, i.e. k = log₂ n: T(1) + c·log₂ n → Θ(log n)

Binary search, certified — the halving loop of Notes 02 and this recurrence are the same argument in two costumes. Note where the logarithm comes from: it is the number of times you can halve n before hitting the base case, nothing more mysterious than that.

Two calls, shrinking by division, linear glue

T(n) = 2T(n/2) + cn = 2[2T(n/4) + c·n/2] + cn = 4T(n/4) + cn + cn = 8T(n/8) + 3·cn … after k steps: 2ᵏ·T(n/2ᵏ) + k·cn base case at k = log₂ n: n·T(1) + cn·log₂ n → Θ(n log n)

The line worth staring at is the middle one: doubling the number of subproblems exactly cancels the halving of their size, so every layer of the unrolling costs the same cn. A perfectly balanced payroll, log₂ n layers deep. This is the sort silhouette's equation, and Θ(n log n) is why next week's merge sort earns its keep.

Two calls, shrinking by subtraction

T(n) = 2T(n−1) + c = 4T(n−2) + 2c + c = 8T(n−3) + 4c + 2c + c … after k steps: 2ᵏ·T(n−k) + (2ᵏ − 1)·c base case at k = n: 2ⁿ·T(0) + (2ⁿ − 1)·c → Θ(2ⁿ)

Hanoi — and the sharpest lesson of the section, best seen by comparing this unrolling with the previous one. Both double the number of subproblems per level. The difference is entirely in what shrinks: dividing the size gives the doubling only log₂ n levels to compound, subtracting gives it n. Doubling tamed by halving is n log n; doubling fed by mere subtraction is 2ⁿ. (For Hanoi the exponential is not a flaw of the algorithm but a fact of the problem — the puzzle provably requires 2ⁿ − 1 moves — which is a useful reminder that a lower bound on the problem excuses any algorithm that merely matches it.)

Check yourself — Unroll T(n) = T(n−1) + cn, T(0) = 0. What is the answer, and which loop pattern from Notes 02 has the same sum?

Unrolling sheds cn, then c(n−1), then c(n−2), …: T(n) = c·(n + (n−1) + ⋯ + 1) = c·n(n+1)/2 = Θ(n²). It is the triangular loop's sum, met one week later wearing recursion. (You will meet this recurrence for real in Week 4: it is selection sort — and quicksort on its worst-case input, a fact that will matter.)


Section 5Recursion trees

Unrolling is algebra; the recursion tree is the same computation as a picture, and the picture generalizes better. For a recurrence of the form T(n) = a·T(n/b) + f(n) — a subproblems, each a b-th of the size, f(n) of local glue — draw the calls as a tree and bill each node for its local work only:

T(n) is the sum of the level bills, and here is the insight that turns a page of algebra into one question. For polynomial glue f(n) = nᵏ, consecutive level bills differ by a constant factor: each level has a× the nodes, each doing (1/bᵏ)× the work, so the bills form a geometric series with ratio r = a / bᵏ. Geometric series have exactly three behaviors, and each one is a running-time verdict:

RatioThe levels…Who pays the billT(n)
r < 1 (a < bᵏ)shrink geometricallythe root — the series is dominated by its first termΘ(nᵏ)
r = 1 (a = bᵏ)all cost the sameeveryone equally — nᵏ per level, log_b n levelsΘ(nᵏ log n)
r > 1 (a > bᵏ)grow geometricallythe leaves — the last term dwarfs the restΘ(nlog_b a)

Three quick sightings before you drive it yourself. The sort silhouette, 2T(n/2) + n: r = 2/2¹ = 1, every level bills cn — the balanced payroll of §4, now visible as the middle row. T(n) = 2T(n/2) + n²: r = 2/4 = ½, the level bills fall n², n²/2, n²/4, … and the whole tree costs at most 2n² — the root essentially is the cost, Θ(n²). T(n) = 4T(n/2) + n: r = 4/2 = 2, the bills grow n, 2n, 4n, … until the leaves — all nlog₂ 4 = n² of them — dominate: Θ(n²). Same answer as the previous example, reached from the opposite end of the tree; the tree tells you not just the class but where the time goes, which is design information. A root-heavy algorithm wants its glue optimized; a leaf-heavy one wants its base case optimized, or fewer subproblems.

Demonstration 2Recursion-tree explorer — which levels carry the cost?
a = 2 b = f(n) =
depth m =
Level-sum ratio a / bᵏ
Leaves · n^log_b a
Verdict
Why do the leaves number n^log_b a?

The tree is log_b n levels deep and multiplies its node count by a at each level, so the leaf count is alog_b n. Now a two-line identity: take log_b of both alog_b n and nlog_b a and both give (log_b a)(log_b n) — so the two expressions are equal. Writing it as nlog_b a puts n in the base, which is what lets us compare it against the glue nᵏ on equal terms.

Sanity checks: a = 4, b = 2 gives n² leaves — start with one instance, quadruple the count every halving, and by the time size-1 instances appear there are n² of them. a = 1 gives n⁰ = 1 leaf: a lone chain of calls, like binary search.

Check yourself — For T(n) = 3T(n/2) + n: what is the ratio r, which levels dominate, and what is T(n)? Set the explorer to check yourself.

r = 3/2¹ = 1.5 > 1: the level bills grow, and the leaves dominate. T(n) = Θ(nlog₂ 3) ≈ Θ(n1.585) — strictly better than the n² you would get from four subproblems, strictly worse than n log n. That strange exponent is not a curiosity: shaving one recursive subproblem off a four-way split is precisely the trick behind the fast integer-multiplication algorithm you will meet in the divide-and-conquer unit, and log₂ 3 is its price tag.

Section 6The substitution method

Trees and unrollings find answers; the substitution method certifies them. It is induction — the same induction as Notes 01 §6.2 — aimed at a bound instead of at correctness, and the workflow is: guess the answer (usually from a quick tree), then prove T(n) ≤ c·(the guess) by strong induction, keeping the constant honest. One full worked example, because there is a subtlety worth catching live.

Claim. If T(n) ≤ 2T(n/2) + n with T(2) ≤ 2, then T(n) ≤ c·n·log₂ n for all n ≥ 2, with c = 2.

Inductive step. Assume the bound for all smaller arguments; in particular T(n/2) ≤ c·(n/2)·log₂(n/2). Then:

T(n) ≤ 2·[c·(n/2)·log₂(n/2)] + n = c·n·(log₂ n − 1) + n = c·n·log₂ n − (c·n − n)

The payoff line: since c = 2 ≥ 1, the parenthesis (c·n − n) is non-negative, so T(n) ≤ c·n·log₂ n. The induction closes exactly — same constant out as in.

Base. T(2) ≤ 2 ≤ c·2·log₂ 2 = 4.

Notice what the proof needed: room. We did not just barely squeeze under the bound — we landed under it with c·n − n to spare, and that slack is what absorbed the "+ n" of glue. When an attempted substitution proof keeps almost working, the standard fix is to strengthen the guess by subtracting a lower-order term (try proving T(n) ≤ c·n·log₂ n − d·n) — counterintuitively, a stronger claim can be easier to prove, because the induction hands you a stronger hypothesis too.

The classic cheat — worth one exam point every year

Here is a "proof" that T(n) = 2T(n/2) + n is O(n). Guess T(n) ≤ cn. Inductive step: T(n) ≤ 2·c·(n/2) + n = cn + n = O(n). Done?

No. The obligation was T(n) ≤ c·n — the exact form, the same constant. What came out was (c+1)·n, a bigger constant, and a constant that grows by 1 at every level of the induction is not a constant at all; summed over log n levels it silently rebuilds the n log n we pretended to avoid. The substitution method's one iron rule: the induction must close on exactly the form you assumed. "… = O(n)" at the end of an inductive step is not a conclusion, it is a confession.


Section 7The Master Theorem

Section 5 ended with one question deciding everything: for T(n) = a·T(n/b) + f(n), compare the glue f(n) against the leaf count nlog_b a and see which one wins — or whether they tie. The Master Theorem is that observation, promoted to a theorem and hardened against the cases where f is not a clean power. Call W(n) = nlog_b a the watershed; the theorem is a three-way comparison against it.

The Master Theorem

Let T(n) = a·T(n/b) + f(n) with constant a ≥ 1 and b > 1, and let W(n) = nlog_b a.

  1. Leaves win. If f(n) = O(nlog_b a − ε) for some ε > 0 — f is polynomially smaller than W — then T(n) = Θ(nlog_b a).
  2. A tie. If f(n) = Θ(nlog_b a), then T(n) = Θ(nlog_b a · log n).
  3. Root wins. If f(n) = Ω(nlog_b a + ε) for some ε > 0, and f satisfies the regularity condition a·f(n/b) ≤ c·f(n) for some c < 1 and large n, then T(n) = Θ(f(n)).

Do not memorize this as three arbitrary formulas — you already own the picture. Case 1 is the tree with growing level sums (leaves dominate), case 2 is the balanced payroll (every level equal, log n levels), case 3 is the shrinking series (root dominates), and the ε's and the regularity condition are the lawyers' language making "genuinely smaller / genuinely bigger / well-behaved" precise. When f(n) = nᵏ, everything collapses back to the ratio test: k versus log_b a decides the case, the polynomial gap is automatic, and regularity always holds. Two applications in full sentences, then a battery in table form:

The sort silhouette, T(n) = 2T(n/2) + n. Watershed: nlog₂ 2 = n. The glue is Θ(n) — exactly the watershed. Case 2: T(n) = Θ(n log n). (With f = Θ(W), no ε is needed; ties are decided without lawyers.)

T(n) = 2T(n/2) + n². Watershed: n. The glue n² is polynomially larger — ε = 1 works. Regularity: a·f(n/b) = 2·(n/2)² = n²/2 ≤ ½·f(n) ✓. Case 3: T(n) = Θ(n²). The recursion is a rounding error on the root's own bill.

RecurrenceWatershed n^(log_b a)Compare fCaseT(n)
T(n) = T(n/2) + 1 (binary search)n⁰ = 1f = Θ(1) — tie2Θ(log n)
T(n) = 2T(n/2) + n (the sort)ntie2Θ(n log n)
T(n) = 4T(n/2) + nf smaller (ε = 1)1Θ(n²)
T(n) = 3T(n/2) + nn1.585f smaller1Θ(nlog₂ 3)
T(n) = 9T(n/3) + nf smaller1Θ(n²)
T(n) = 2T(n/2) + n²nf larger, regular3Θ(n²)
Demonstration 3Master Theorem checker — the comparison that decides the case
a = 2 b = f(n) = nᵏ, k = 1
Watershed exponent · log_b a
Case
T(n)
The extended case 2, for glue with log factors

A widening of the tie handles glue like n log n that the sliders above cannot reach: if f(n) = Θ(nlog_b a · logk n) with k ≥ 0, then T(n) = Θ(nlog_b a · logk+1 n). So T(n) = 2T(n/2) + n log n — a shape you will meet in Week 10 — solves to Θ(n log² n): the tie's answer, with one more logarithm stacked on. Note that the basic theorem's cases 1 and 3 genuinely do not apply to that recurrence, since log n is neither polynomially small nor polynomially large; without the extension, the basic theorem is simply silent (§8).

Check yourself — Strassen's algorithm multiplies two n×n matrices with seven recursive multiplications of half-size matrices plus Θ(n²) of additions: T(n) = 7T(n/2) + n². Solve it, and answer the question that made it famous: does beating eight multiplications matter?

Watershed: nlog₂ 7 ≈ n2.807. The glue n² is polynomially smaller (ε ≈ 0.807), so case 1: T(n) = Θ(nlog₂ 7). The naive eight-multiplication split gives 8T(n/2) + n² → Θ(n³) — no better than three nested loops. One multiplication saved, and the exponent of the whole algorithm drops from 3 to 2.807. In a leaf-dominated recurrence the count of subproblems lives in the exponent; that is why shaving a from 8 to 7 was publishable, and it is your set-up for a puzzle to bring Thursday: why did shaving glue constants achieve nothing while shaving one subproblem achieved everything?

Section 8When the theorem is silent

The Master Theorem is a lookup table for one specific family — constant a of equal-size subproblems of size n/b with well-behaved glue. Outside the family it says nothing at all, and recognizing "outside" on sight matters as much as applying the theorem inside. The usual escapes, with the tool that handles each:

The toolkit, in the order you should reach for it

1 — If the recurrence matches a·T(n/b) + f(n), try the Master Theorem; it is instant. 2 — If it does not match, or f is unusual, draw the recursion tree and sum the levels; it almost always yields a confident guess. 3 — If the answer will carry weight (a report, an exam, a design decision), certify the guess by substitution. 4 — For subtract-and-conquer shapes, just unroll. Nothing in this course's recurrences resists all four.

Section 9Before next session

To do — due before Tuesday, September 22
  1. Review Notes 03, and redo the three unrollings of §4 on paper, without looking. The skill is fluency, not familiarity — Thursday's sorting analyses assume it.
  2. Project 1 work begins now. By Tuesday you should have: the repository set up, the benchmarking harness running (Notes 02 §6), and at least one algorithm implemented with its specification and tests. Bring one measured curve, however rough — we will critique methodology in class.
  3. Solve and bring: (a) T(n) = 2T(n/2) + n³ · (b) T(n) = T(n−2) + c · (c) T(n) = 5T(n/4) + n. Identify the tool as well as the answer.
  4. Optional puzzle, from the Strassen check: in a case-1 recurrence, why does improving the glue's constant change nothing while removing one subproblem changes the exponent? One sentence, in terms of the recursion tree.

Next week the machinery earns its keep. Insertion sort, merge sort, and quicksort — specified, proved, priced with this week's tools, and raced against each other with Notes 02's harness. Merge sort is T(n) = 2T(n/2) + cn; quicksort is the unequal-split story of §8 with a randomized twist; and the crossover between them at small n is the one you explored in Notes 02's last demonstration. It all connects. That is Notes 04.