Divide and Conquer Paradigm
Divide and conquer (D&C) is one of the four foundational algorithm-design paradigms taught in every introductory algorithms course alongside greedy, dynamic programming, and backtracking. Its recipe is universal and short: (1) divide the problem into smaller sub-problems of the same kind, (2) conquer each sub-problem by recursive application of the same algorithm (or solve directly when small enough), (3) combine the sub-solutions into a solution for the original problem. The technique dates back at least to John von Neumann’s 1945 description of merge sort in the First Draft of a Report on the EDVAC — the first divide-and-conquer algorithm in modern computer science. The paradigm’s mathematical signature is a recurrence relation of the form
T(n) = a · T(n/b) + f(n), solved by the Master Theorem (or, for non-uniform sub-problem sizes, by the Akra-Bazzi method). D&C wins decisively when (a) the problem decomposes naturally into independent sub-problems of the same structural form, (b) the combine step is asymptotically cheap relative to the conquer step, and (c) the sub-problems do not overlap. When sub-problems overlap, we are in dynamic programming territory and should memoize. When the combine step does all the work, we have a degenerate recurrence and gain nothing. Canonical D&C algorithms include Merge Sort (T(n) = 2T(n/2) + Θ(n)→Θ(n log n)), Quicksort (same recurrence on average), Binary Search (T(n) = T(n/2) + Θ(1)→Θ(log n)), Karatsuba Multiplication (T(n) = 3T(n/2) + Θ(n)→Θ(n^{log₂ 3})), Strassen’s Algorithm (T(n) = 7T(n/2) + Θ(n²)→Θ(n^{log₂ 7})), FFT (T(n) = 2T(n/2) + Θ(n)→Θ(n log n)), and Closest Pair of Points (T(n) = 2T(n/2) + Θ(n)→Θ(n log n)). The paradigm’s deeper conceptual significance: it exposes the structure of a problem by repeated halving, making latent regularities visible. Many D&C algorithms (Karatsuba, Strassen, FFT) achieve sub-quadratic complexity precisely because divide-and-conquer reveals an algebraic identity that lets the combine step do fewer sub-problems than the obvious approach.
1. Intuition — The Three-Step Recipe
The paradigm is captured in three lines:
divide_and_conquer(problem):
if problem is small enough:
return base_case_solution(problem) # CONQUER (base case)
sub_problems := divide(problem) # DIVIDE
sub_solutions := [
divide_and_conquer(s) for s in sub_problems # CONQUER (recursion)
]
return combine(sub_solutions) # COMBINE
A real-world analogy: imagine sorting a deck of 1000 playing cards. Sorting all 1000 at once is unwieldy. So you divide the deck in half, give each half to a friend, and ask each friend to sort their 500 cards (recursion!). When they hand back two sorted halves, you merge them — you have two sorted piles and you repeatedly take the smaller top card and place it in the output pile. The merge takes 1000 comparisons (linear in deck size). The total work, modulo recursion overhead, is 1000 · log₂ 1000 ≈ 10,000 comparisons — versus the ~500,000 of the naive Θ(n²) insertion sort. This is Merge Sort, and it’s the prototypical D&C win.
The paradigm’s effectiveness depends on three structural properties of the problem:
- Self-similar decomposition. The sub-problems must be instances of the same problem type. Sorting 500 cards is the same kind of problem as sorting 1000. This is the “recursion-friendly” structure.
- Independence. The sub-problems must be solvable without consulting one another. If the answer to one sub-problem depends on the answer to another, you’ve left D&C territory.
- Cheap combine. The combine step must be fast enough that it doesn’t dominate. If combining two sub-solutions costs as much as solving them from scratch, the divide step gained you nothing.
When all three hold, D&C is often the natural algorithm — and frequently the asymptotically optimal one.
2. The Master Theorem Lens
Most D&C recurrences have the form
T(n) = a · T(n/b) + f(n)
where a ≥ 1 is the number of sub-problems, b > 1 is the size-shrinkage factor (each sub-problem is 1/b-th the size of the parent), and f(n) is the combine cost.
The Master Theorem classifies the asymptotics by comparing f(n) against n^{log_b a} (the work done at the leaves):
- Case 1 — leaf-heavy. If
f(n) = O(n^{log_b a − ε})for someε > 0, the leaves dominate andT(n) = Θ(n^{log_b a}). The combine is asymptotically irrelevant — almost all the work is at the bottom of the recursion. Examples: Karatsuba (a=3, b=2, f(n)=O(n),n^{log₂ 3} = n^{1.585}dominates) and Strassen (a=7, b=2, f(n)=O(n²),n^{log₂ 7} = n^{2.807}dominates). - Case 2 — balanced. If
f(n) = Θ(n^{log_b a} · log^k n)fork ≥ 0, work is balanced across levels andT(n) = Θ(n^{log_b a} · log^{k+1} n). Examples: Merge Sort (a=2, b=2, f(n)=Θ(n) = Θ(n^{log₂ 2}), givesΘ(n log n)) and Binary Search (a=1, b=2, f(n)=Θ(1) = Θ(n^{log₂ 1}) = Θ(1), givesΘ(log n)). - Case 3 — root-heavy (combine-heavy). If
f(n) = Ω(n^{log_b a + ε})and the regularity conditiona · f(n/b) ≤ c · f(n)for somec < 1holds, the root dominates andT(n) = Θ(f(n)). Example: a recurrence likeT(n) = 2T(n/2) + Θ(n²)givesT(n) = Θ(n²)because the linear-quadratic combine swamps the leaf work.
The Master Theorem fails when sub-problem sizes are non-uniform (e.g., T(n) = T(n/3) + T(2n/3) + Θ(n)); the Akra-Bazzi method (Akra & Bazzi, 1998) generalizes to such cases.
3. When D&C Wins, When It Loses
3.1 D&C Wins (3 + 1 conditions)
D&C is the right hammer when:
- The problem decomposes naturally. Sorting decomposes into “sort halves and merge”; convex-hull computation decomposes into “compute hulls of halves and merge”; matrix multiplication decomposes into block multiplications.
- Sub-problems are independent. No shared state between siblings.
- Combine is asymptotically cheap. Often
O(n)orO(n log n). Iff(n) = Θ(n^{log_b a}), you’re balanced (Case 2) and pay alog npremium; iff(n) = O(n^{log_b a − ε})you’re leaf-heavy (Case 1) and the combine is free asymptotically. - There’s an algebraic identity that reduces the number of sub-problems below the obvious count. This is the deeper structural win — Karatsuba (4 → 3), Strassen (8 → 7), FFT (n² → n log n via roots of unity). The “obvious” naive D&C with no identity often gives the same asymptotic as the iterative naive algorithm; the win comes from the identity.
3.2 D&C Loses
D&C is the wrong hammer when:
- Sub-problems overlap. The same sub-problem is solved many times. Example: naive recursion for the
n-th Fibonacci number computesF(n−2)once forF(n)and again forF(n−1), doubling at every level — exponential time. The fix is dynamic programming: cache sub-solutions and reuse them. The general rule: if the same sub-problem appears in multiple branches of the recursion tree, switch to DP. - Combine is as expensive as the original problem. If solving two halves and merging them costs
Θ(n²), you haven’t reduced anything compared to a directΘ(n²)algorithm. Example: a naive recurrenceT(n) = 2T(n/2) + Θ(n²)givesΘ(n²)— same as not bothering with D&C. - The base case is trivial but the recursion overhead dominates. For very small
n, the function-call overhead, allocation, and stack pushes of recursion can dwarf the asymptotic savings. Production sorts like Tim Sort and Intro Sort use insertion sort belown ≈ 16precisely because the constant factors of insertion sort are smaller for tiny arrays. - The problem is inherently sequential. Problems with strict left-to-right data dependencies (e.g., processing a stream where each element depends on all previous elements’ aggregated state) resist D&C. You can’t process the second half until you’ve finished the first half.
3.3 D&C vs Backtracking vs DP
| Paradigm | When | Asymptotic | Common signature |
|---|---|---|---|
| D&C | Independent sub-problems, cheap combine | T(n) = a T(n/b) + f(n) | Master Theorem |
| DP | Overlapping sub-problems, optimal substructure | T(n) = Σ T(sub) + Θ(combine) with memoization | Polynomial-time solution to NP-style brute force |
| Backtracking | Search through exponential space with pruning | T(n) = O(b^d) with branching b, depth d | Constraint satisfaction (N-Queens, Sudoku) |
| Greedy | Locally optimal choice yields global optimum | T(n) = Θ(n log n) typically | Matroids, exchange argument |
The relationships are tight. DP is “D&C with caching for overlapping sub-problems.” Backtracking is “D&C plus pruning when the sub-problems are search nodes with constraints.” Greedy is a special case where the divide step makes a single irrevocable choice.
4. Tiny Worked Example — Counting Inversions
An inversion in an array is a pair (i, j) with i < j but A[i] > A[j]. Counting inversions naively is Θ(n²). D&C gives Θ(n log n) — the algorithm is a small modification of Merge Sort.
The recipe:
- Divide the array in half.
- Conquer by recursively counting inversions in each half.
- Combine by counting “cross-inversions” (pairs where one element is in the left half, the other in the right) during a modified merge step. Whenever, in the merge, an element from the right half is placed before some elements still pending in the left half, all those pending left elements are larger, so each contributes one inversion.
Example: A = [2, 4, 1, 3, 5].
Divide: L = [2, 4], R = [1, 3, 5].
Recurse:
inversions(L) = 0(already sorted).inversions(R) = 0(already sorted).
Combine — merge L and R:
- Compare
2and1:1 < 2, so place1. PendingL = [2, 4](2 elements). Cross-inversions += 2. - Compare
2and3:2 < 3, so place2. PendingL = [4](1 element). - Compare
4and3:3 < 4, so place3. PendingL = [4]. Cross-inversions += 1. - Compare
4and5:4 < 5, so place4. PendingL = []. - Place remaining
5.
Cross-inversions = 2 + 1 = 3. Total inversions = 0 + 0 + 3 = 3.
Verify by enumeration: pairs (2, 1), (4, 1), (4, 3) — exactly 3 inversions. ✓
The recurrence: T(n) = 2 T(n/2) + Θ(n) (merge is O(n)), giving Θ(n log n) by Master Theorem Case 2.
5. Pseudocode — Generic D&C Skeleton
divide_and_conquer(problem):
# Base case — handle small problems directly.
if size(problem) <= base_case_threshold:
return solve_directly(problem)
# Divide — split into a sub-problems each of size n / b.
sub_problems := divide(problem) # |sub_problems| = a, each size n/b
# Conquer — recursively solve each sub-problem.
sub_solutions := [divide_and_conquer(sp) for sp in sub_problems]
# Combine — assemble the sub-solutions into a global solution.
return combine(sub_solutions, problem) # O(f(n)) work
Concrete instantiations:
| Algorithm | a | b | divide | combine | Recurrence | Asymptotic |
|---|---|---|---|---|---|---|
| Merge Sort | 2 | 2 | split in halves | merge two sorted halves O(n) | T(n) = 2 T(n/2) + Θ(n) | Θ(n log n) |
| Quicksort (average) | 2 | 2 | partition around pivot | concatenate (free) | T(n) = 2 T(n/2) + Θ(n) | Θ(n log n) |
| Binary Search | 1 | 2 | half the array | trivial | T(n) = T(n/2) + Θ(1) | Θ(log n) |
| Karatsuba Multiplication | 3 | 2 | high/low halves | linear add/sub Θ(n) | T(n) = 3 T(n/2) + Θ(n) | Θ(n^{1.585}) |
| Strassen’s Algorithm | 7 | 2 | 4 quadrants | 18 block adds Θ(n²) | T(n) = 7 T(n/2) + Θ(n²) | Θ(n^{2.807}) |
| FFT — Fast Fourier Transform | 2 | 2 | even/odd indices | combine via twiddles Θ(n) | T(n) = 2 T(n/2) + Θ(n) | Θ(n log n) |
| Closest Pair of Points | 2 | 2 | split by median x | strip merge Θ(n) | T(n) = 2 T(n/2) + Θ(n) | Θ(n log n) |
| Maximum Subarray (CLRS §4.1) | 2 | 2 | halves | crossing-max Θ(n) | T(n) = 2 T(n/2) + Θ(n) | Θ(n log n) (improvable to Θ(n) via Kadane’s Algorithm) |
6. Python — Generic D&C Template + Counting Inversions
from typing import Callable, TypeVar
T = TypeVar('T') # problem type
S = TypeVar('S') # solution type
def divide_and_conquer(
problem: T,
*,
is_base: Callable[[T], bool],
base: Callable[[T], S],
divide: Callable[[T], list[T]],
combine: Callable[[list[S], T], S],
) -> S:
"""Generic divide-and-conquer driver. Pure-Python, recursion-based."""
if is_base(problem):
return base(problem)
sub_problems = divide(problem)
sub_solutions = [
divide_and_conquer(
sp,
is_base=is_base, base=base,
divide=divide, combine=combine,
)
for sp in sub_problems
]
return combine(sub_solutions, problem)
# ----- Concrete instantiation: count inversions in O(n log n) -----
def count_inversions(arr: list[int]) -> tuple[list[int], int]:
"""
Return (sorted_arr, inversion_count). Modified merge sort:
every time we 'pull from the right half' during merge, the pending
elements in the left half are each larger, contributing |left_pending|
inversions.
"""
if len(arr) <= 1:
return arr[:], 0
mid = len(arr) // 2
left, inv_l = count_inversions(arr[:mid])
right, inv_r = count_inversions(arr[mid:])
merged: list[int] = []
cross = 0
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
merged.append(left[i]); i += 1
else:
# right[j] is smaller than every remaining left[i..end-1]
# — each contributes one inversion.
merged.append(right[j])
cross += len(left) - i
j += 1
merged.extend(left[i:])
merged.extend(right[j:])
return merged, inv_l + inv_r + cross
# Sanity test
if __name__ == "__main__":
arr = [2, 4, 1, 3, 5]
sorted_arr, inv = count_inversions(arr)
assert sorted_arr == [1, 2, 3, 4, 5]
assert inv == 3
print("ok")The crucial line is cross += len(left) - i — it counts all remaining left-half elements that are larger than the just-placed right[j]. This is the algebraic insight that makes inversion counting O(n log n) instead of O(n²): rather than checking every pair across halves explicitly, the merge step amortizes the count into one update per right-element placed.
7. Common Recurrences and Their Solutions
The set of recurrences that arise from “natural” D&C algorithms is small. Memorize these:
T(n) = T(n/2) + Θ(1) → Θ(log n) [Binary Search]
T(n) = T(n/2) + Θ(n) → Θ(n) [linear-time selection of one half]
T(n) = 2 T(n/2) + Θ(1) → Θ(n) [tree traversal-style]
T(n) = 2 T(n/2) + Θ(n) → Θ(n log n) [Merge Sort, Closest Pair, FFT]
T(n) = 2 T(n/2) + Θ(n²) → Θ(n²) [combine dominates; D&C gains nothing]
T(n) = 3 T(n/2) + Θ(n) → Θ(n^{log₂ 3}) ≈ n^{1.585} [Karatsuba]
T(n) = 4 T(n/2) + Θ(n) → Θ(n²) [naive D&C matrix mult — same as schoolbook]
T(n) = 7 T(n/2) + Θ(n²) → Θ(n^{log₂ 7}) ≈ n^{2.807} [Strassen]
T(n) = 8 T(n/2) + Θ(n²) → Θ(n³) [naive D&C matrix mult — same as schoolbook]
T(n) = T(n − 1) + Θ(1) → Θ(n) [linear recursion, e.g., factorial]
T(n) = T(n − 1) + Θ(n) → Θ(n²) [insertion sort, naive selection sort]
T(n) = 2 T(n − 1) + Θ(1) → Θ(2ⁿ) [Tower of Hanoi, naive Fibonacci]
T(n) = T(n/3) + T(2n/3) + Θ(n) → Θ(n log n) [Akra-Bazzi; non-uniform split]
Reading these off becomes second nature after exposure. The pattern: when sub-problem sizes shrink by a constant factor b > 1 and there are a sub-problems, the leaf count is a^{log_b n} = n^{log_b a}, which is the lowest possible asymptotic. Whether the combine work pushes you above that depends on f(n) vs n^{log_b a} — the Master Theorem tells you which dominates.
8. Diagram — The Recursion Tree
flowchart TD R["T(n)<br/>combine: f(n)"] R --> A1["T(n/b)"] R --> A2["T(n/b)"] R --> A3["...<br/>(a children)"] R --> A4["T(n/b)"] A1 --> B11["T(n/b²)"] A1 --> B12["T(n/b²)"] A1 --> B13["...<br/>(a children)"] A4 --> B41["T(n/b²)"] A4 --> B42["T(n/b²)"] A4 --> B43["..."] style R fill:#fed style A1 fill:#fea style A2 fill:#fea style A3 fill:#fea style A4 fill:#fea style B11 fill:#bfb style B12 fill:#bfb style B13 fill:#bfb style B41 fill:#bfb style B42 fill:#bfb style B43 fill:#bfb
What this diagram shows. A generic divide-and-conquer recursion tree. The root represents the top-level problem of size n with combine cost f(n). It spawns a child sub-problems, each of size n/b. Each child recursively spawns a grandchildren of size n/b². The tree has branching factor a, depth log_b n, and a^{log_b n} = n^{log_b a} leaves. The total work is the sum over all levels of (number of nodes at this level) × (combine cost per node). This sum has three cases: leaf-heavy (Case 1, f(n) polynomially smaller than n^{log_b a}), balanced (Case 2, equal up to log factors), and root-heavy (Case 3, f(n) polynomially larger). The geometric series defining the sum determines which level dominates and gives the Master Theorem cases.
9. The “Recombination Trick” — Why D&C Beats Iteration
A naive iterative algorithm and a naive D&C algorithm often have the same asymptotic complexity. The asymptotic win of D&C comes when divide-and-conquer exposes an algebraic structure that lets the combine step do less work than the obvious decomposition.
Three paradigm examples:
- Karatsuba (1962): Naive multiplication of
n-digit integers isΘ(n²). Split each integer at the middle digit; the obvious D&C gives 4 sub-multiplications of half-size, recurrenceT(n) = 4T(n/2) + Θ(n) = Θ(n²)— no win. Karatsuba’s identity replaces 4 sub-multiplications with 3 plus extra additions, recurrenceT(n) = 3T(n/2) + Θ(n) = Θ(n^{log₂ 3})— sub-quadratic. - Strassen (1969): Naive matrix multiplication is
Θ(n³). Block partition gives 8 sub-multiplications, recurrenceT(n) = 8T(n/2) + Θ(n²) = Θ(n³)— no win. Strassen’s identity replaces 8 with 7 plus extra additions, recurrenceT(n) = 7T(n/2) + Θ(n²) = Θ(n^{log₂ 7})— sub-cubic. - FFT (Cooley-Tukey 1965): Naive DFT is
Θ(n²). Split by even/odd indices, exploit the algebraic identity thatω^{2k} = (ω²)^k(whereωis then-th primitive root of unity) to write the size-nDFT as two size-n/2DFTs plusO(n)combination — recurrenceT(n) = 2T(n/2) + Θ(n) = Θ(n log n).
The pattern: D&C is most powerful when the recursion tree’s branching factor a can be reduced below the obvious value via an algebraic identity in the combine step. Without such an identity, naive D&C often gives the same asymptotic as iteration.
10. Pitfalls
- Choosing D&C when sub-problems overlap. The classic pitfall: writing
fib(n) = fib(n-1) + fib(n-2)and being shocked it takes exponential time. Sub-problems overlap massively. Fix: memoize, turning the recurrence into linear time. The general rule: if a sub-problem can appear in more than one branch of the recursion tree, you need DP, not D&C. - Choosing D&C when the combine step is too expensive. If
f(n) = Ω(n^{log_b a + ε}), the combine dominates and D&C reduces to “spendf(n)time at the root, then recurse trivially.” There’s no asymptotic gain. Example:T(n) = 2T(n/2) + Θ(n²) = Θ(n²)— same as a directΘ(n²)algorithm. - Misapplying the Master Theorem. The theorem requires
b > 1(constant size shrinkage),a ≥ 1, andf(n)polynomially separated fromn^{log_b a}for Cases 1 and 3. There are gaps — recurrences wheref(n) = Θ(n^{log_b a} · (log n)^{−1})for instance — that fall through the theorem’s cases and require Akra-Bazzi or first-principles analysis. - Forgetting the regularity condition in Case 3. Master Theorem Case 3 requires
a · f(n/b) ≤ c · f(n)for somec < 1. Without this, Case 3 conclusionT(n) = Θ(f(n))may not hold even whenf(n)is polynomially larger thann^{log_b a}. - Recursion overhead on small inputs. D&C recursing all the way to
n = 1pays per-call overhead at every level. Production code uses a threshold base case — whennfalls below ~16–64, switch to insertion sort, schoolbook multiplication, or whatever simple algorithm has the smallest constant factor. Tim Sort and Intro Sort both do this. - Stack overflow on deep recursion. Recursion depth is
Θ(log n)for D&C — usually well within the default stack limit. But for unbalanced D&C (Quicksort worst case, where one side hasn − 1elements), depth can beΘ(n)and overflow the stack. Mitigations: tail-call optimization (where supported), iterative recursion via explicit stack, or pivot-randomization to avoid the bad case in expectation. - Inefficient divide step. The divide step itself can be expensive. For Merge Sort on a linked list, the divide step (find the middle) is
O(n), but only one child is recursed on the head pointer — you must split the list explicitly to avoid one half re-walking the other. Subtle bugs come from sloppy splitting. - Ignoring cache effects. D&C is often very cache-friendly because sub-problems fit in cache once they shrink past the cache size. This is why naive matrix multiplication is much slower than tiled or recursive D&C in practice — even when both are
Θ(n³). The asymptotic equivalence hides a 5–10× constant-factor cache advantage for D&C. - Confusing D&C with recursion in general. Recursion is a control structure; D&C is a paradigm. Tree traversals, backtracking, and many DP formulations use recursion but are not D&C — they don’t divide into independent sub-problems of the same kind, or they don’t combine, or they have overlapping sub-problems.
- Believing D&C parallelizes for free. It often does, because sub-problems are independent. But the combine step may sequentially depend on all sub-solutions, creating an Amdahl’s-law bottleneck. For Merge Sort, the merge step is sequential
O(n); parallel merge sort needs a parallel merge algorithm to achieve theoretical linear speedup.
11. Common Interview Problems
| Problem | Source | D&C variant |
|---|---|---|
| Merge Sort | Classic | T(n) = 2T(n/2) + Θ(n) |
| Quicksort | Classic | T(n) = 2T(n/2) + Θ(n) average |
| Binary Search | Classic | T(n) = T(n/2) + Θ(1) |
| Karatsuba Multiplication | CLRS Problem 4-3 | T(n) = 3T(n/2) + Θ(n) |
| Strassen’s Algorithm | CLRS §4.2 | T(n) = 7T(n/2) + Θ(n²) |
| Closest Pair of Points | CLRS §33.4 | T(n) = 2T(n/2) + Θ(n) |
| Count Inversions | Classic | Modified merge sort, Θ(n log n) |
| Maximum Subarray | LeetCode 53; CLRS §4.1 | D&C Θ(n log n); Kadane Θ(n) |
| Median of Two Sorted Arrays | LeetCode 4 | Binary-search-style D&C, Θ(log(m+n)) |
| Construct Binary Tree from Inorder + Preorder | LeetCode 105 | D&C on subarrays |
| Different Ways to Add Parentheses | LeetCode 241 | Recursive split + combine |
| Skyline Problem | LeetCode 218 | D&C merge of skyline halves, Θ(n log n) |
| Reverse Pairs | LeetCode 493 | Modified merge sort, Θ(n log n) |
| Burst Balloons | LeetCode 312 | Interval DP — but the paradigm is D&C-flavor |
12. Open Questions
- Is there a problem where D&C achieves a strictly better asymptotic than is achievable by any iterative or DP algorithm? Conjecturally yes for many problems (e.g., matrix multiplication’s
Θ(n^{2.371…})is currently achievable only via algebraic D&C-flavored algorithms), but lower bounds are notoriously hard. - How does cache-oblivious D&C (Frigo et al. 1999) interact with the Master Theorem? Cache-oblivious D&C achieves optimal cache complexity for a wide class of problems without knowing the cache size in advance — a beautiful result built atop the D&C paradigm.
- When does the Akra-Bazzi method give a tighter bound than the Master Theorem? Always, on Master-Theorem-amenable inputs they agree; Akra-Bazzi additionally handles non-uniform splits and non-polynomial
f(n). - Can D&C be combined with DP in a hybrid? Yes — divide-and-conquer DP is a class of optimizations (Knuth’s optimization, divide-and-conquer DP optimization for
dp[i][j] = min_{k} dp[i-1][k] + cost(k, j)with quadrangle inequality) that exploit monotonicity to drop aO(n)factor from a 2D DP. See Knuth’s Optimization (planned). - Why does the Master Theorem work with constant
bbut not when sub-problem sizes are non-constant? The proof requires the geometric series inn / b^kto be well-behaved; non-uniform splits break the simple geometric structure and require Akra-Bazzi’s integral-based machinery.
13. See Also
- Master Theorem — solves the canonical D&C recurrence
- Merge Sort — prototypical D&C;
Θ(n log n)sorting - Quicksort — D&C with random partition;
Θ(n log n)average - Binary Search — D&C with one sub-problem
- Karatsuba Multiplication — sub-quadratic integer multiplication via D&C
- Strassen’s Algorithm — sub-cubic matrix multiplication via D&C
- FFT — Fast Fourier Transform —
Θ(n log n)polynomial multiplication via D&C - Closest Pair of Points —
Θ(n log n)geometric D&C - Memoization vs Tabulation — DP, the technique of choice when D&C sub-problems overlap
- Backtracking Framework — D&C variant for search-with-pruning
- Greedy Algorithms — Proof Techniques — alternative paradigm; greedy + D&C are sometimes interchangeable
- Recurrence Relations — broader theory of solving such recurrences
- Big-O Notation
- Big-Omega and Big-Theta
- Recursion vs Iteration
- Median of Medians — D&C selection in worst-case linear time
- SWE Interview Preparation MOC