Overview
Divide and conquer solves a problem by splitting it into independent smaller subproblems of the same type, solving each recursively, and combining their answers. Merge sort, quicksort, binary search, fast exponentiation, and closest pair of points all follow this shape.
It differs from dynamic programming because the subproblems do not overlap: each piece is solved once. The cost is analyzed with a recurrence, such as T(n) = 2T(n/2) + O(n) for merge sort, which the Master Theorem solves to O(n log n).
Split the pile between two assistants, who each split theirs again, until everyone holds one paper. Then pairs merge their sorted piles, and the merged piles are merged again until one sorted pile remains.
When to use it
- The input splits naturally into halves or independent parts.
- The combine step is cheaper than solving the whole problem directly.
- Counting problems that can be solved during a merge (inversions, reverse pairs).
- Building balanced structures from sorted data, or tree problems with left and right halves.
Problem patterns it solves
Recognize it when: sorting or counting that can happen during a merge.
- 912. Sort an Array
- 148. Sort List
- 315. Count of Smaller Numbers After Self
- 493. Reverse Pairs
Recognize it when: each step reduces the problem to one half.
- 50. Pow(x, n)
- 704. Binary Search
- 4. Median of Two Sorted Arrays
Recognize it when: construct a tree or structure from a middle split.
- 108. Convert Sorted Array to BST
- 654. Maximum Binary Tree
- 105. Construct Binary Tree from Preorder and Inorder
Recognize it when: the best answer may cross the middle.
- 53. Maximum Subarray (divide and conquer)
- 241. Different Ways to Add Parentheses
- 932. Beautiful Array
Recognize it when: merge lists pairwise in rounds.
- 23. Merge k Sorted Lists
Where it is used in real software
External merge sort splits data into memory-sized chunks, sorts each, and merges them; MapReduce jobs follow the same split-and-combine model.
The FFT divides a signal into even and odd parts recursively, powering audio processing, image compression, and fast polynomial multiplication.
Java's Fork/Join framework and parallel streams split work recursively across CPU cores and combine results.
Closest pair of points and convex hull algorithms achieve O(n log n) with divide and conquer.
Key terms
- Divide
- Split the input into smaller independent parts.
- Conquer
- Solve each part recursively; tiny parts are base cases.
- Combine
- Merge the partial answers into the full answer.
- Recurrence
- T(n) = aT(n/b) + f(n), describing the total cost.
How it works, step by step
- 1Define the base case
An empty or single-element input is solved directly.
- 2Split
Usually at the middle: mid = (lo + hi) >> 1.
- 3Solve both halves recursively
Trust that each returns a correct answer for its half.
- 4Combine
Merge sorted halves, sum counts, or compute the answer that crosses the middle.
- 5Analyze with the Master Theorem
Compare the number of subproblems with the cost of combining.
Counting inversions with merge sort
arr = [2, 4, 1, 3, 5]; an inversion is i < j with arr[i] > arr[j]
| Merge | Left | Right | Inversions found | Merged |
|---|---|---|---|---|
| 1 | [2] | [4] | 0 | [2, 4] |
| 2 | [3] | [5] | 0 | [3, 5] |
| 3 | [1] | [3, 5] | 0 | [1, 3, 5] |
| 4 | [2, 4] | [1, 3, 5] | 1 < 2: 2 left elements remain (2 pairs); 3 < 4: 1 remains (1 pair) | [1, 2, 3, 4, 5] |
NOWMerge: 1 | Left: [2] | Right: [4] | Inversions found: 0 | Merged: [2, 4]
Total inversions = 3: (2,1), (4,1), (4,3). When an element from the right half is placed first, it is smaller than every remaining element in the left half, so all of them form inversions at once. O(n log n) instead of O(n^2).
Implementation
function countInversions(arr) { function sortCount(a) { if (a.length <= 1) return [a, 0]; const mid = a.length >> 1; const [left, leftCount] = sortCount(a.slice(0, mid)); const [right, rightCount] = sortCount(a.slice(mid)); const merged = []; let i = 0, j = 0, cross = 0; while (i < left.length && j < right.length) { if (left[i] <= right[j]) merged.push(left[i++]); else { merged.push(right[j++]); cross += left.length - i; // every remaining left element is larger } } return [merged.concat(left.slice(i), right.slice(j)), leftCount + rightCount + cross]; } return sortCount(arr)[1];} // 50. Pow(x, n): T(n) = T(n/2) + O(1) = O(log n)function myPow(x, n) { if (n === 0) return 1; if (n < 0) return 1 / myPow(x, -n); const half = myPow(x, Math.floor(n / 2)); return n % 2 === 0 ? half * half : half * half * x;} // 23. Merge k Sorted Lists by merging pairs in rounds: O(N log k)function mergeKLists(lists) { if (!lists.length) return null; while (lists.length > 1) { const next = []; for (let i = 0; i < lists.length; i += 2) next.push(mergeTwo(lists[i], lists[i + 1] ?? null)); lists = next; } return lists[0];} function mergeTwo(a, b) { const dummy = { next: null }; let tail = dummy; while (a && b) { if (a.val <= b.val) { tail.next = a; a = a.next; } else { tail.next = b; b = b.next; } tail = tail.next; } tail.next = a ?? b; return dummy.next;}Complexity and performance
T(n) = 2T(n/2) + O(n).
T(n) = T(n/2) + O(1).
T(n) = 2T(n/2) + O(1).
Recursion depth plus merge buffers.
Trade-offs
If subproblems overlap, divide and conquer recomputes them; add memoization and it becomes DP (as with Different Ways to Add Parentheses).
For small inputs, switching to a simple algorithm (insertion sort under ~16 elements) is faster.
Independent subproblems can run in parallel, a big practical advantage.
Variants and related techniques
Solve only one smaller subproblem: binary search, quickselect.
Reduce the number of recursive multiplications to beat the naive complexity.
Offline technique for problems with three ordering dimensions.
Common mistakes
- Unbalanced splits.
Fix: Quicksort with bad pivots degrades to O(n^2); split at the middle or randomize.
- Copying arrays with slice at every level.
Fix: Pass index ranges and a shared buffer to reduce memory and allocation.
- Missing the crossing case.
Fix: When the answer can span both halves (max subarray), compute it explicitly in the combine step.
Interview questions
How is divide and conquer different from dynamic programming?
Divide and conquer subproblems are independent and each solved once. DP applies when the same subproblems repeat, so results are cached and reused.
Why is merge sort O(n log n)?
The array halves log n times, and at each level the merges together touch all n elements, so the total work is n x log n.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| 50. Pow(x, n) | Medium | Halving recursion. |
| 912. Sort an Array | Medium | Merge sort. |
| 148. Sort List | Medium | Merge sort on a list. |
| 241. Different Ways to Add Parentheses | Medium | Split at operators. |
| 23. Merge k Sorted Lists | Hard | Pairwise merge rounds. |
| 493. Reverse Pairs | Hard | Count during merge. |