DSA DSA In Depth
0%
lesson recurrencesmaster-theoremdivide-and-conquer

Solving Recurrences: Recursion Trees, Substitution & Master Theorem

7 min read Module 1.1: Asymptotic Analysis, Recurrences & Memory Hierarchy Updated Oct 23, 2026
📄 Cheat Sheet
Master Theorem in Algorithms for Dividing Recurrence Functions
Abdul Bari Master Theorem in Algorithms for Dividing Recurrence Functions Watch on YouTube ↗

Systematically derives the three cases of the Master Theorem for T(n) = aT(n/b) + f(n) by comparing leaf work n^(log_b a) against root driving work f(n).

0:00 / --:--

The Recursion Tree Geometric Series & The Watershed Function $n^{\log_b a}$

Consider the recurrence $T(n) = a\,T(n/b) + f(n)$ where $a \ge 1$ and $b > 1$ are constants. At depth $j$ of the recursion tree ($0 \le j \le \log_b n$), there are $a^j$ nodes, each solving a subproblem of size $n / b^j$ and performing $f(n / b^j)$ local work. Summing across all levels gives the exact total runtime:

$$T(n) = \Theta\left(n^{\log_b a}\right) + \sum_{j=0}^{\log_b n - 1} a^j f\left(\frac{n}{b^j}\right)$$

Here, the number of leaves at the bottom of the tree is $a^{\log_b n} = n^{\log_b a}$ (by the logarithmic identity $a^{\log_b n} = (b^{\log_b a})^{\log_b n} = n^{\log_b a}$). We call $n^{\log_b a}$ the watershed function. Comparing $f(n)$ to $n^{\log_b a}$ yields the three regimes of the Master Theorem (including the generalized polylogarithmic Case 2):

Master Regime Condition on $f(n)$ vs Watershed $n^{\log_b a}$ Closed-Form Solution $T(n)$ Work Distribution Across Tree Classic Algorithm
Case 1 (Leaf-Heavy) $f(n) = O(n^{\log_b a - \epsilon})$ for some $\epsilon > 0$ $\Theta(n^{\log_b a})$ Geometrically increasing down tree; leaves dominate Strassen $7T(n/2) + \Theta(n^2) \implies \Theta(n^{\log_2 7})$
Case 2 (Balanced Levels) $f(n) = \Theta(n^{\log_b a} \log^k n)$ for $k \ge 0$ $\Theta(n^{\log_b a} \log^{k+1} n)$ Every level performs equal work $\Theta(n^{\log_b a} \log^k n)$ Mergesort $2T(n/2) + \Theta(n) \implies \Theta(n \log n)$
Case 2b ($k = -1$ Border) $f(n) = \Theta(n^{\log_b a} / \log n)$ ($k = -1$) $\Theta(n^{\log_b a} \log \log n)$ Harmonic decay across $\log_b n$ levels ($\sum 1/j$) $2T(n/2) + \Theta(n / \log n) \implies \Theta(n \log \log n)$
Case 3 (Root-Heavy) $f(n) = \Omega(n^{\log_b a + \epsilon})$ & $a f(n/b) \le c f(n)$ ($c < 1$) $\Theta(f(n))$ Geometrically decreasing down tree; root dominates Quickselect Avg $T(n/2) + \Theta(n) \implies \Theta(n)$

Formal Specification: Polynomial Gap Requirement & The Substitution Method

In Case 1 and Case 3, $f(n)$ must be polynomially smaller or larger than $n^{\log_b a}$ by a factor of $n^{\pm \epsilon}$ for some constant $\epsilon > 0$. If $f(n) = n^{\log_b a} / \log n$, the ratio is $\log n = o(n^\epsilon)$ for every $\epsilon > 0$—falling into the chasm between Case 1 and Standard Case 2 (resolved by generalized Case 2 with $k = -1$). Furthermore, Case 3 requires the Regularity Condition: $\exists c < 1$ such that $a f(n/b) \le c f(n)$ for all $n \ge n_0$.

Strengthening the Inductive Hypothesis (Substitution Method): Suppose $T(n) = 2T(n/2) + 1$ and we want to prove $T(n) \le c n$. Substituting the inductive hypothesis gives $T(n) \le 2(c n / 2) + 1 = c n + 1$, which fails to prove $T(n) \le c n$! To make induction succeed, we must subtract a lower-order term: hypothesize $T(n) \le c n - d$. Then $T(n) \le 2(c(n/2) - d) + 1 = c n - 2d + 1 \le c n - d$ for all $d \ge 1$.

Visualizing the Three Master Theorem Work Profiles

The SVG diagram below contrasts the per-level work across depth $j \in [0, \log_b n]$ for Case 1 (expanding geometric series), Case 2 (constant rectangular column), and Case 3 (contracting geometric series).

Case 1: Leaf-Heavy f(n) ≪ n^(log_b a) Leaves dominate sum T(n) = Θ(n^(log_b a)) Case 2: Balanced Levels f(n) = Θ(n^(log_b a) log^k n) log_b(n) equal levels Θ(n^(log_b a) log^(k+1) n) Case 3: Root-Heavy f(n) ≫ n^(log_b a) Root level dominates sum T(n) = Θ(f(n))
flowchart TD Start["Recurrence T(n) = a T(n/b) + f(n)"] --> W["Compute Watershed w(n) = n^(log_b a)"] W --> C1{"Is f(n) = O(n^(log_b a - ε)) ?"} C1 -- "Yes (Polynomially Smaller)" --> R1["Case 1: T(n) = Θ(n^(log_b a))"] C1 -- "No" --> C2{"Is f(n) = Θ(n^(log_b a) log^k n) ?"} C2 -- "Yes (k ≥ 0)" --> R2["Case 2: T(n) = Θ(n^(log_b a) log^(k+1) n)"] C2 -- "Yes (k = -1)" --> R2B["Case 2b: T(n) = Θ(n^(log_b a) log log n)"] C2 -- "No" --> C3{"Is f(n) = Ω(n^(log_b a + ε)) AND a f(n/b) ≤ c f(n)?"} C3 -- "Yes (Polynomially Larger + Regular)" --> R3["Case 3: T(n) = Θ(f(n))"] C3 -- "No" --> Fall["Use Recursion Tree / Akra-Bazzi / Substitution"]

Hardware Reality: Recursion Tree Base-Case Coarsening ($n_{\text{leaf}} = 32\text{–}64$)

In a naive recursive implementation of Mergesort or Strassen's algorithm that recurses all the way down to $n = 1$, Case 1 and Case 2 show that the recursion tree generates $\Theta(n^{\log_b a})$ leaves. In fact, in a binary recursion tree ($a = 2, b = 2$), 50% of all recursive function calls occur at the very bottom leaf level ($n = 1$), and 75% occur in the bottom two levels! If you coarsen the base case to switch to an in-place iterative kernel (like Insertion Sort for Mergesort or an AVX-512 register micro-kernel for GEMM) at $n_{\text{base}} = 32$, you prune the bottom $\log_2 32 = 5$ levels of the recursion tree, eliminating $96.875\%$ ($31/32$) of all function call frames while keeping each leaf tile completely inside L1 cache.

Change of Variables for Non-Linear Recurrences

Some recurrences do not shrink by a constant divisor $n/b$, such as $T(n) = 2T(\sqrt{n}) + \log_2 n$ (which arises in van Emde Boas trees). By substituting $m = \log_2 n$ (so $n = 2^m$ and $\sqrt{n} = 2^{m/2}$) and defining $S(m) = T(2^m)$, the recurrence transforms into a standard Master Theorem form:

$$S(m) = 2S(m/2) + m \implies S(m) = \Theta(m \log m) \implies T(n) = \Theta(\log n \log \log n)$$

// C++20: Coarsened Divide-and-Conquer with L1-Cache Base Case (Eliminating 97% of Call Frames)
#include <algorithm>
#include <cstddef>
#include <span>

// Base case threshold: 32 int64_t elements = 256 bytes (4 L1 cache lines)
constexpr size_t kLeafThreshold = 32;

void insertionSortBase(std::span<int64_t> a) noexcept {
    for (size_t i = 1; i < a.size(); ++i) {
        int64_t key = a[i];
        size_t j = i;
        while (j > 0 && a[j - 1] > key) {
            a[j] = a[j - 1];
            --j;
        }
        a[j] = key;
    }
}

// Recurrence: T(n) = 2 T(n/2) + Theta(n), pruned at n <= 32
void coarsenedMergeSort(std::span<int64_t> a, std::span<int64_t> scratch) noexcept {
    const size_t n = a.size();
    if (n <= kLeafThreshold) {
        insertionSortBase(a); // Prunes bottom 5 levels of recursion tree!
        return;
    }
    const size_t mid = n / 2;
    coarsenedMergeSort(a.first(mid), scratch.first(mid));
    coarsenedMergeSort(a.subspan(mid), scratch.subspan(mid));

    // Skip merge if already ordered across boundary (Best-Case Theta(n))
    if (a[mid - 1] <= a[mid]) return;

    std::merge(a.first(mid).begin(), a.first(mid).end(),
               a.subspan(mid).begin(), a.subspan(mid).end(),
               scratch.begin());
    std::copy(scratch.begin(), scratch.begin() + n, a.begin());
}
Recursion Tree, Master Theorem & Akra-Bazzi Level-Work Visualizer
n^(log_b a) vs f(n)

Compare per-level work a^d · f(n/b^d) across Balanced Case 2 (Merge Sort), Leaf-Heavy Case 1 (Karatsuba/Strassen), and uneven Akra-Bazzi T(n) = T(n/3) + T(2n/3) + Θ(n).

Step 1 / 7
Custom Input:
Timeline

Invariant & State

Master Theorem Case 2: n^(log_b a) = n^(log_2 2) = n == Θ(f(n)). Work per level is invariant at c·n across log_2(n) levels.
d=0: 1 × c·n = c·n d=1: c·(n/2) d=1: c·(n/2) (∑=c·n) All log₂(n)+1 levels do exactly c·n work ⇒ Total = Θ(n log n)
Branching / Shrink
a = 2, b = 2
Work per Level d
2^d · c(n/2^d) = c·n
Total Work
Θ(n log n)
[Step 1] Balanced recursion tree: 2^d subproblems of size n/2^d sum to c·n at every depth d.

Failure Modes & Adversarial Pitfalls

  • Applying the Master Theorem When $a$ Is Not Constant: Given $T(n) = 2^n T(n/2) + n^n$, engineers sometimes compute $\log_b a = \log_2(2^n) = n$, which is nonsense because $a$ must be a fixed constant independent of $n$.
  • Sloppy Inductive Substitution (O Inside Induction): Never write "Assume $T(k) \le O(k)$" in an inductive proof! The hidden constant inside $O(k)$ changes between the inductive hypothesis and the inductive step, allowing one to "prove" false statements like $T(n) = 2T(n/2) + n \implies O(n)$. Always name the explicit constant $c$ and prove $T(n) \le c n - d$ for the exact same $c$.
Check yourself Question 1 of 4
Concept Check Number of Leaves & Watershed Function in a Recursion Tree recall

In the divide-and-conquer recurrence `T(n) = a T(n/b) + f(n)` with `a ≥ 1` and `b > 1`, what is the recursion tree depth and how many leaf subproblems exist at the bottom level?

⚡ Interactive Challenge · Staff Level

Algorithmic Design & Complexity Check

Analyze the invariant, asymptotic bound, and hardware behavior before selecting.

You want to prove by mathematical induction that `T(n) = 2 T(⌊n/2⌋) + 1` satisfies `T(n) ∈ O(n)`. When you test the inductive hypothesis `T(k) ≤ c·k`, the inductive step yields `T(n) ≤ 2(c·(n/2)) + 1 = c·n + 1`, which fails to be `≤ c·n`. How do you fix this proof rigorously?

Key Takeaways

In T(n) = a T(n/b) + f(n), the recursion tree has depth log_b(n) and exactly a^(log_b n) = n^(log_b a) leaves, defining the watershed function.
Master Theorem Case 1 (leaf-heavy) and Case 3 (root-heavy) require a polynomial gap n^(±ε) between f(n) and n^(log_b a); polylogarithmic gaps fall into generalized Case 2.
When proving an upper bound via the Substitution Method fails by a lower-order additive term (e.g., cn + 1), strengthen the inductive hypothesis by subtracting a lower-order term (cn - d).
Coarsening the recursion base case from n = 1 to n = 32 prunes the bottom 5 levels of a binary recursion tree, eliminating 96.9% of function call overhead.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue