G27
Self-checkedTier 1 10 min readalgo.divide-and-conquer

Divide and conquer

The divide and conquer paradigm: split a problem into independent smaller copies, solve them recursively, combine the answers. The recurrence T(n) = aT(n/b) + f(n), what the three Master theorem cases mean for where the work is done, the maximum-and-minimum example (3n/2 - 2 comparisons), correctness by induction, stack depth, and why overlapping subproblems need dynamic programming instead.

Why it exists

Many problems have the same shape at every scale: sorting a million numbers is two problems of sorting half a million plus some work to put the halves together. Divide and conquer turns that observation into an algorithm and into a recurrence. It is the source of the fastest known algorithms for sorting, searching ordered data, multiplying large numbers and matrices, finding the median, and many geometric problems. It also gives the questions of the syllabus a standard form: write the recurrence, then solve it.

Prerequisites

You need recursion and the call stack (recursion and the call stack) and the methods for solving recurrences (solving recurrences).

Precise definitions

Divide and conquer solves a problem of size nn in three steps:

  1. Divide the input into aa smaller subproblems of the same kind, each of size about n/bn/b (the subproblems are independent: they do not share work).
  2. Conquer each subproblem recursively. A subproblem smaller than a fixed constant (the base case) is solved directly.
  3. Combine the answers of the subproblems into the answer of the whole problem.

The recurrence. If dividing and combining together cost f(n)f(n), the running time satisfies T(n)=a T(n/b)+f(n),T(n)=Θ(1) for small n.T(n) = a\,T(n/b) + f(n), \qquad T(n) = \Theta(1) \text{ for small } n. With a=1a = 1 the method is sometimes called decrease and conquer (binary search, fast exponentiation): only one subproblem is solved.

Correctness is proved by strong induction on nn: the base case is correct; if the recursive calls are correct for all sizes less than nn, and the combine step turns correct answers of the parts into a correct answer of the whole, then the call for nn is correct.

Depth and space. If the problem is cut into pieces of size n/bn/b the recursion depth is ⌈log⁡bn⌉\lceil \log_b n \rceil, so the stack uses O(log⁡n)O(\log n) frames (plus whatever the algorithm stores in each frame, for example a temporary array in merge sort).

How it works

The skeleton.

solve(problem P of size n):
    if n is small: return the answer directly            /* base case */
    split P into a parts of size about n / b             /* divide: cost counted in f(n) */
    for each part: answer[i] = solve(part)               /* conquer: a recursive calls */
    return combine(answer[0..a-1])                       /* combine: cost counted in f(n) */

An example: maximum and minimum. To find the largest and the smallest of nn keys, a plain scan that updates both uses 2(n−1)2(n-1) comparisons. Divide and conquer: split the array into two halves, find the maximum and minimum of each, then compare the two maxima and the two minima (two comparisons). Two keys need only one comparison. The recurrence for the number of comparisons is T(1)=0,T(2)=1,T(n)=2 T(n/2)+2,T(1) = 0,\quad T(2) = 1,\quad T(n) = 2\,T(n/2) + 2, and for nn a power of two the solution is T(n)=3n/2−2T(n) = 3n/2 - 2 (check: T(2)=1T(2) = 1, T(4)=4T(4) = 4, T(8)=10T(8) = 10). The code below is what the animation runs; only the divide and conquer function is shown and the array is 31,7,45,12,50,3,28,1931, 7, 45, 12, 50, 3, 28, 19.

Maximum and minimum by divide and conquer

Real run

C source

1#include <stdio.h>
2
3/* finds the largest and smallest of a[lo..hi] */
4void max_min(const int a[], int lo, int hi, int *max, int *min) {
5 if (lo == hi) {
6 *max = *min = a[lo];
7 return;
8 }
9 if (hi == lo + 1) {
10 if (a[lo] > a[hi]) {
11 *max = a[lo];
12 *min = a[hi];
13 } else {
14 *max = a[hi];
15 *min = a[lo];
16 }
17 return;
18 }
19 int mid = (lo + hi) / 2;
20 int max1, min1, max2, min2;
21 max_min(a, lo, mid, &max1, &min1);
22 max_min(a, mid + 1, hi, &max2, &min2);
23 *max = max1 > max2 ? max1 : max2;
24 *min = min1 < min2 ? min1 : min2;
25}

Array a

31
0
7
1
45
2
12
3
50
4
3
5
28
6
19
7

Counts

comparisons so far = 0 (a plain scan would use 2(n-1) = 14, this method uses 3n/2 - 2 = 10)

Blue = the position this step is working on, amber = a position being compared, red = positions just swapped, green = finished or in its final place, a filled blue cell = a value just placed, faded = not part of the structure at this step.

Step 1

Find the largest and smallest of 8 keys.

1 / 26
The array of 8 keys is set up by hidden driver code. max_min() is the exact code that runs. Faded cells are outside the range of the call being traced. Produced by: real execution: every array, stack, matrix and list in the picture is read from the running program's memory at each step. Apple clang version 21.0.0 (clang-2100.1.1.101). gcc -O0 -o prog prog.c && ./prog

The table gives the measured comparison counts for n=2,4,…,64n = 2, 4, \ldots, 64 next to the plain scan.

Maximum and minimum: scanning versus divide and conquer: start

Computed by a script, not typed
nplain scan: 2(n - 1)divide and conquer (measured)3n/2 - 2saved
2
4
8
16
32
64

Step 0

Both methods find the largest and the smallest of n keys, n a power of two. The divide and conquer count is measured by running max_min (the code of the animation) with a counter.

0 / 6
computed by script: the quantities are measured by running C, or evaluated, one row at a time. python3 scripts/gen-algo-sort-traces.py

Two remarks. First, for nn that is not a power of two the same code can use a comparison more than 3n/2−23n/2 - 2: for n=6n = 6 it uses 88, not 77, so the formula is for powers of two. Second, the saving does not come from recursion as such: comparing the keys in pairs first and then taking the larger of each pair for the maximum and the smaller for the minimum gives the same 3n/2−23n/2 - 2 with a loop. Divide and conquer is a way to find such an idea, not always the only way to write it.

Where the work is done. The recursion tree of T(n)=a T(n/b)+f(n)T(n) = a\,T(n/b) + f(n) has aia^i subproblems of size n/bin/b^i on level ii, so level ii costs aif(n/bi)a^i f(n/b^i), and there are log⁡bn\log_b n levels. The Master theorem (solving recurrences) compares f(n)f(n) with nlog⁡ban^{\log_b a} (the number of leaves):

  • ff grows polynomially slower than nlog⁡ban^{\log_b a}: the levels cost more and more going down, the leaves dominate, T(n)=Θ(nlog⁡ba)T(n) = \Theta(n^{\log_b a}). Example: maximum and minimum, a=2a = 2, b=2b = 2, f=2f = 2: Θ(n)\Theta(n). Strassen, Karatsuba.
  • ff grows like nlog⁡ban^{\log_b a}: every level costs about the same, T(n)=Θ(f(n)log⁡n)T(n) = \Theta(f(n) \log n) (for f=Θ(nlog⁡ba)f = \Theta(n^{\log_b a})). Examples: merge sort, binary search (a=1a = 1, f=1f = 1: Θ(log⁡n)\Theta(\log n)).
  • ff grows polynomially faster (with the regularity condition): the root dominates, T(n)=Θ(f(n))T(n) = \Theta(f(n)). Example: T(n)=2T(n/2)+n2T(n) = 2T(n/2) + n^2.

For the balanced case the table of a recursion tree makes it visible: every level costs nn.

Recursion tree of T(n) = 2T(n/2) + n: start

Computed by a script, not typed
levelsubproblemssize of eachcost of onecost of the level
0
1
2
3
4
5
6
7
8
9
10

Step 0

Every level of the tree of T(n) = 2T(n/2) + n costs the same, n: the number of subproblems doubles while their size halves. Here n = 1024, so there are 11 levels.

0 / 12
computed by script: the quantities are evaluated or measured, one row at a time. python3 scripts/gen-algo-traces.py

How the choices change the answer. Reducing the number of subproblems matters more than the cost of combining: T(n)=4T(n/2)+nT(n) = 4T(n/2) + n is Θ(n2)\Theta(n^2) but T(n)=3T(n/2)+nT(n) = 3T(n/2) + n is Θ(n1.585)\Theta(n^{1.585}) (Karatsuba's idea), and 7T(n/2)+n27T(n/2) + n^2 is Θ(n2.807)\Theta(n^{2.807}) against 8T(n/2)+n2=Θ(n3)8T(n/2) + n^2 = \Theta(n^3) (Strassen's idea). Both are on the next page.

Unbalanced division. Splitting into very unequal parts makes the tree deep: T(n)=T(n−1)+nT(n) = T(n-1) + n is Θ(n2)\Theta(n^2) (the worst case of quicksort), T(n)=T(n/3)+T(2n/3)+nT(n) = T(n/3) + T(2n/3) + n is still Θ(nlog⁡n)\Theta(n \log n) because the depth stays logarithmic.

When divide and conquer is the wrong tool. The subproblems must be independent. The recursion for Fibonacci numbers, F(n)=F(n−1)+F(n−2)F(n) = F(n-1) + F(n-2), makes two calls that both compute F(n−2)F(n-2) and so on: the running time T(n)=T(n−1)+T(n−2)+1T(n) = T(n-1) + T(n-2) + 1 is exponential, Θ(φn)\Theta(\varphi^n) with φ=1.618\varphi = 1.618. When subproblems overlap, remember their answers: that is dynamic programming.

Worked examples

EX 1Divide and conquer that gains nothing

Easy

Summing nn numbers by splitting in halves, adding the two partial sums: T(n)=2T(n/2)+1T(n) = 2T(n/2) + 1, which is Θ(n)\Theta(n) by case 1 (leaves dominate: nlog⁡22=nn^{\log_2 2} = n against f=1f = 1). It is exactly the work of a loop (every number is added once), but it uses a stack of depth log⁡2n\log_2 n instead of a single variable, and its two halves could be computed on different processors. Recursion does not make an algorithm faster by itself.

EX 2Count the comparisons of max and min

Easy

For n=8n = 8: the array is cut into 4+44 + 4, each into 2+22 + 2. There are four pairs, one comparison each (44), then two combine steps at the middle level of two comparisons each (44), then one at the top of two comparisons (22): 4+4+2=10=3⋅8/2−24 + 4 + 2 = 10 = 3 \cdot 8/2 - 2, against 2⋅7=142 \cdot 7 = 14 for the plain scan. The animation shows the running total ending at 1010.

EX 3The number of subproblems decides the exponent

Medium

Compare T(n)=4T(n/2)+nT(n) = 4T(n/2) + n, T(n)=3T(n/2)+nT(n) = 3T(n/2) + n and T(n)=2T(n/2)+nT(n) = 2T(n/2) + n. In each case f(n)=nf(n) = n, and nlog⁡2an^{\log_2 a} is n2n^2, n1.585n^{1.585} and nn. The first two have ff polynomially smaller than the leaf count, so Θ(n2)\Theta(n^2) and Θ(n1.585)\Theta(n^{1.585}) (leaves dominate); the third has ff equal to it, so Θ(nlog⁡n)\Theta(n \log n). Going from 44 to 33 subproblems lowers the exponent from 22 to 1.5851.585; going to 22 removes the polynomial gap altogether.

EX 4Overlapping subproblems

Medium

The call tree of fib(n) = fib(n - 1) + fib(n - 2) has T(n)=T(n−1)+T(n−2)+1T(n) = T(n-1) + T(n-2) + 1 calls, growing like φn\varphi^n: Θ(1.618n)\Theta(1.618^n). A divide and conquer on independent halves would give a polynomial bound; here the two calls repeat each other's work. Storing the answers (memoization) brings it down to Θ(n)\Theta(n) additions (dynamic programming).

EX 5Recurrences of unusual shape

GATE level

(a) T(n)=T(n/3)+T(2n/3)+nT(n) = T(n/3) + T(2n/3) + n: the Master theorem does not apply; the tree has depth between log⁡3n\log_3 n and log⁡3/2n\log_{3/2} n and every level costs at most nn, so Θ(nlog⁡n)\Theta(n \log n). (b) T(n)=n T(n)+nT(n) = \sqrt n\,T(\sqrt n) + n: divide by nn and write S(n)=T(n)/nS(n) = T(n)/n: S(n)=S(n)+1S(n) = S(\sqrt n) + 1, which is Θ(log⁡log⁡n)\Theta(\log \log n), so T(n)=Θ(nlog⁡log⁡n)T(n) = \Theta(n \log \log n) (confirmed numerically). (c) An algorithm splits the input into three parts of size n/3n/3 and combines in O(n)O(n): T(n)=3T(n/3)+n=Θ(nlog⁡n)T(n) = 3T(n/3) + n = \Theta(n \log n). (d) Three parts of size n/2n/2 and combine in O(n)O(n): 3T(n/2)+n=Θ(n1.585)3T(n/2) + n = \Theta(n^{1.585}). Always compute nlog⁡ban^{\log_b a} first.

Cheat box

Cheat box

  • Divide, conquer (independent subproblems), combine. T(n)=a T(n/b)+f(n)T(n) = a\,T(n/b) + f(n).
  • Compare f(n)f(n) with nlog⁡ban^{\log_b a}: smaller (leaves) →Θ(nlog⁡ba)\to \Theta(n^{\log_b a}); equal →Θ(f(n)log⁡n)\to \Theta(f(n) \log n); larger with regularity (root) →Θ(f(n))\to \Theta(f(n)).
  • Max and min by divide and conquer: T(n)=2T(n/2)+2T(n) = 2T(n/2) + 2, 3n/2−23n/2 - 2 comparisons for n=2kn = 2^k (against 2n−22n - 2).
  • Depth log⁡bn\log_b n; stack space O(log⁡n)O(\log n) for balanced splits, O(n)O(n) for a chain.
  • Fewer subproblems is better than a cheaper combine: 4→34 \to 3 (Karatsuba), 8→78 \to 7 (Strassen).
  • Overlapping subproblems ⇒\Rightarrow dynamic programming; T(n)=T(n−1)+T(n−2)+1T(n) = T(n-1) + T(n-2) + 1 is exponential.

Traps and GATE patterns

Connections

The simplest instance with one subproblem is binary search. The classic two-subproblem algorithms are merge sort and quicksort, and the standard collection of applications (Strassen, Karatsuba, closest pair, maximum subarray, selection) is on the next page, divide and conquer applications. Recurrences are solved by the methods of solving recurrences; repeating subproblems lead to dynamic programming.

PYQs

5 previous year questions from your book. I re-solved each one myself (1 by running or simulating, 4 by reasoning only); the book's key is shown next to my answer. All PYQs

GATE 2007 · MCQ · 1 markQ32 · page 102 of the book
Exponentiation is a heavily used operation in
public key cryptography. Which of the following
options is the tightest upper bound on the number
of multiplications required to compute bnmod
m,0≤b , n≤m?
  • (a)O(logn)
  • (b)O(√n )
  • (c)O(n/logn)
  • (d)O(n)
GATE 2019 · MCQ · 2 marksQ6 · page 108 of the book
There are n unsorted arrays: A 1 , A 2 , ..., A n .
Assume that n is odd. Each of A 1 , A 2 , ..., A n
contains n distinct elements. There are no common
elements between any two arrays. The worst-case
time complexity of computing the median of the
medians of A 1 , A 2 , ..., A n is
  • (a)O(n)
  • (b)O(n log n)
  • (c)O(n2)
  • (d)Ω(n2 log n)
GATE 2017 · Set 1 · MCQ · 2 marksQ4 · page 146 of the book
Consider the following table:

Algorithms
Design
Paradigms
P
Kruskal
i
Divide and
Conquer
Q
Quicksort
ii
Greedy
R
Floyd-
Warshall
iii Dynamic
Programming
  • (a)P-(ii), Q-(iii), R-(i)
  • (b)P-(iii), Q-(i), R-(ii)
  • (c)P-(ii), Q-(i), R-(iii)
  • (d)P-(i), Q-(ii), R-(iii)
GATE 2015 · Set 2 · MCQ · 1 markQ6 · page 147 of the book
Given below are some algorithms, and some
algorithm design paradigms.
List - I
P. Dijkstra's Shortest Path
Q. Floyd-Warshall algorithm to compute all
pairs shortest path
R. Binary search on a sorted array
S. Backtracking search on a graph
List – II
i. Divide and Conquer
ii. Dynamic Programming
iii. Greedy design
iv. Depth-first search
v. Breadth-first search
Match the above algorithms on the left to the
corresponding design paradigm they follow.
  • (a)P-i, Q-iii, R-i, S-v.
  • (b)P-iii, Q-iii, R-i, S-v.
  • (c)P-iii, Q-ii, R-i, S-iv.
  • (d)P-iii, Q-ii, R-i, S-v.
GATE 2015 · Set 1 · MCQQ7 · page 147 of the book
Match the following:

(P) Prim's algorithm for
minimum spanning tree
(i) Backtracking
(Q) Floyd-Warshall
algorithm for all pairs
shortest paths
(ii) Greedy
method
(R) Mergesort
(iii) Dynamic
programming
(S) Hamiltonian circuit
(iv) Divide and
conquer
  • (a)P-iii, Q-ii, R-iv, S-i
  • (b)P-i, Q-ii, R-iv, S-iii
  • (c)P-ii, Q-iii, R-iv, S-i
  • (d)P-ii, Q-i, R-iii, S-iv

Self-check

Question 1

How many comparisons does divide and conquer max-and-min use on 1616 keys?

Show answer

3⋅16/2−2=223 \cdot 16/2 - 2 = 22 (against 2⋅15=302 \cdot 15 = 30 for the plain scan).

Question 2

Solve T(n)=3T(n/3)+nT(n) = 3T(n/3) + n and T(n)=3T(n/3)+1T(n) = 3T(n/3) + 1.

Show answer

nlog⁡33=nn^{\log_3 3} = n. The first has f=nf = n: equal, Θ(nlog⁡n)\Theta(n \log n). The second has f=1f = 1: smaller, Θ(n)\Theta(n).

Question 3

Why is divide and conquer a poor way to compute Fibonacci numbers by F(n)=F(n−1)+F(n−2)F(n) = F(n-1) + F(n-2)?

Show answer

The two subproblems overlap, so the same values are recomputed many times: exponential time. Memoization (dynamic programming) fixes this.

Question 4

What is the recursion depth, and hence the stack use, of a balanced divide and conquer on n=106n = 10^6 keys?

Show answer

About log⁡2106≈20\log_2 10^6 \approx 20 levels, so a stack of about 2020 frames.

Revision card

One-screen revision

  • T(n)=a T(n/b)+f(n)T(n) = a\,T(n/b) + f(n); compare ff with nlog⁡ban^{\log_b a} (leaves / every level / root).
  • Max and min: 2T(n/2)+2⇒3n/2−22T(n/2) + 2 \Rightarrow 3n/2 - 2 (nn a power of two).
  • Subproblems must be independent; overlap ⇒\Rightarrow DP. Depth log⁡bn\log_b n.
  • Fewer subproblems, smaller exponent: 4→34 \to 3, 8→78 \to 7.
  • Unbalanced: T(n−1)+n=Θ(n2)T(n-1) + n = \Theta(n^2); T(n/3)+T(2n/3)+n=Θ(nlog⁡n)T(n/3) + T(2n/3) + n = \Theta(n \log n).

References

  • ·Cormen, Leiserson, Rivest, Stein, Introduction to Algorithms (3rd edition), Chapter 4 Divide-and-Conquer (4 intro, 4.1 maximum subarray) and 2.3.1 The divide-and-conquer approach
  • ·Kleinberg and Tardos, Algorithm Design, Chapter 5 Divide and Conquer (5 intro)
  • ·Dasgupta, Papadimitriou, Vazirani, Algorithms, Chapter 2 Divide-and-conquer algorithms

Verification

The animation is a real run of the C code shown; the comparison counts of the table were measured by running that code with a counter for n = 2 to 64 and equal 3n/2 - 2 (scripts/gen-algo-sort-traces.py), and the recurrence T(n) = 2T(n/2) + 2 was solved exactly in an independent Python check, which also shows that the count is not 3n/2 - 2 for n = 6. The solution of T(n) = sqrt(n) T(sqrt n) + n was confirmed numerically. The Master theorem facts are those of the recurrence page. Textbook chapter references are from memory. SECOND PASS (2026-11-02, done by the assistant, not a human check): every Self-check question was solved before the printed answer was read and all agreed; the definitions, worked examples, cheat box and traps were read claim by claim and the numbers re-derived by hand or by a small script (loop counts, recurrences, merge and quicksort counts and averages, hash probes and load-factor formulas, DP tables, MST and shortest-path runs, Floyd matrices); long C programs were not re-read line by line (they are compiled and compared by npm run verify:c, and their printed outputs were checked against the prose); tables and animations are machine output and were read, not re-run; No other defect found. Status raised to self-checked; verified needs a textbook or human check.

last verified: never