Quant Memo
Core

Divide and Conquer and the Master Theorem

Divide and conquer solves a problem by splitting it into smaller copies of itself. The Master Theorem is a lookup table that tells you the running time without redoing the algebra every time.

Prerequisites: Big-O Complexity, Recursion and the Call Stack

Some problems get easier if you cut them in half, solve each half, and glue the answers back together. Sorting a deck of a thousand cards feels impossible to do directly, but sorting two piles of five hundred and merging them is manageable — and each pile of five hundred splits the same way again, down to piles of one card, which are already sorted. That pattern — split, solve the pieces, combine — is divide and conquer. The Master Theorem is the shortcut for figuring out how long the whole thing takes without tracing every level of the recursion by hand.

The shape of a divide-and-conquer recurrence

Every divide-and-conquer algorithm's running time follows the same template:

T(n)=aT(n/b)+f(n)T(n) = a \, T(n/b) + f(n)

In words: solving a problem of size nn means solving aa smaller subproblems, each of size n/bn/b, and then spending f(n)f(n) time to divide the input and combine the results. For merge sort, a=2a = 2 (two halves), b=2b = 2 (each half is size n/2n/2), and f(n)=nf(n) = n (merging two sorted halves takes linear time).

The Master Theorem compares how fast the subproblem count grows against how expensive the combine step is, and picks whichever dominates.

T(n)={O(nlogba)if f(n) grows slowerO(nlogbalogn)if they grow at the same rateO(f(n))if f(n) grows fasterT(n) = \begin{cases} O(n^{\log_b a}) & \text{if } f(n) \text{ grows slower} \\ O(n^{\log_b a} \log n) & \text{if they grow at the same rate} \\ O(f(n)) & \text{if } f(n) \text{ grows faster} \end{cases}

nlogban^{\log_b a} is the cost if you did nothing but recurse — it counts how many leaf-level subproblems exist. Compare that to f(n)f(n), the cost of the combine step at the top. Whichever term is bigger wins; the tie case adds one extra logn\log n because every level costs the same amount and there are logn\log n levels.

which term wins: leaves vs combine cost case 1: leaves win n^(log_b a) f(n) case 2: tie (+log n) n^(log_b a) f(n) case 3: combine wins n^(log_b a) f(n)
Whichever bar is taller sets the running time. Merge sort and binary search both land in the middle case, where the two costs tie at every level.

Worked example 1: merge sort

Merge sort splits an array in half (a=2,b=2a=2, b=2), recursively sorts each half, then merges in f(n)=O(n)f(n) = O(n). Compute nlogba=nlog22=n1=nn^{\log_b a} = n^{\log_2 2} = n^1 = n. That matches f(n)=nf(n) = n exactly — the tie case — so

T(n)=O(nlogn).T(n) = O(n \log n).

Trace it directly: at depth 0 there is 1 subproblem of size nn, costing nn to merge. At depth 1, 2 subproblems of size n/2n/2, costing 2n/2=n2 \cdot n/2 = n to merge. At depth kk, 2k2^k subproblems of size n/2kn/2^k, still costing nn total to merge at that level. There are log2n\log_2 n levels, each costing nn, giving nlognn \log n overall — exactly what the theorem predicts.

Worked example 2: binary search

Binary search halves the search space and does O(1)O(1) work to decide which half to keep: a=1a=1, b=2b=2, f(n)=O(1)f(n) = O(1). Here nlogba=nlog21=n0=1n^{\log_b a} = n^{\log_2 1} = n^0 = 1, which matches f(n)=O(1)f(n) = O(1) — again the tie case — giving

T(n)=O(1logn)=O(logn).T(n) = O(1 \cdot \log n) = O(\log n).

Check it on 1,024 elements: each comparison halves the range — 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1 — ten halvings, and log21024=10\log_2 1024 = 10. Matches.

merge sort recursion tree — cost per level n n/2 n/2 n/4 n/4 n/4 n/4 level cost: n level cost: n/2+n/2 = n level cost: n log n levels x cost n per level = n log n
Every level of the recursion costs the same total amount, n, because the pieces shrink but there are proportionally more of them. Multiply by the log n levels and you get the running time.
def merge_sort(a):                 # T(n) = 2T(n/2) + O(n) -> O(n log n)
    if len(a) <= 1:
        return a
    mid = len(a) // 2
    left = merge_sort(a[:mid])
    right = merge_sort(a[mid:])
    return merge(left, right)      # O(n) combine step

The Master Theorem is a fast lookup, not new math: compare the recursive branching cost nlogban^{\log_b a} against the combine cost f(n)f(n), and the larger one dominates the total. It only applies to this exact recurrence shape — same-size subproblems, no fancy conditionals in the split.

What this means in practice

Reach for the Master Theorem the moment you write a recurrence during an interview instead of guessing. It also explains real design choices: Karatsuba multiplication turns T(n)=4T(n/2)+O(n)T(n) = 4T(n/2) + O(n) (naive, O(n2)O(n^2)) into T(n)=3T(n/2)+O(n)T(n) = 3T(n/2) + O(n) by trading one multiplication for extra additions — dropping aa from 4 to 3 changes nlog24=n2n^{\log_2 4}=n^2 into nlog23n1.585n^{\log_2 3} \approx n^{1.585}, a real asymptotic win used in some big-integer and polynomial libraries.

The theorem does not apply when subproblems are unequal sizes (like quickselect's worst case), when aa or bb depends on nn, or when f(n)f(n) isn't polynomial-comparable to nlogban^{\log_b a} (e.g., f(n)=n/lognf(n) = n / \log n needs the extended version). Applying the plain three-case formula to those recurrences gives a wrong answer — check the shape first.

Where it shows up

Interviewers ask you to state the recurrence for merge sort, quicksort, and binary search and derive the bound, sometimes cold. In production, the same reasoning tells you whether a recursive pricing or matrix routine (e.g., recursive Strassen-style matrix multiply, or a divide-and-conquer FFT) is going to scale to the sizes you actually run — before you profile it and find out the hard way.

Related concepts

Practice in interviews

Further reading

  • Cormen, Leiserson, Rivest & Stein, Introduction to Algorithms (ch. 4)
ShareTwitterLinkedIn