Articles

Divide and Conquer Algorithms Explained

Divide and conquer solves problems by splitting them into smaller subproblems, solving each recursively, and combining the results.

The Lycoris Team The Lycoris Team · · 5 min read
Abstract illustration representing programming languages

Divide and conquer is an algorithm design strategy that solves a problem by breaking it into smaller subproblems of the same type, solving each subproblem recursively, and then combining their solutions into an answer for the original problem. It’s one of the most widely reused patterns in algorithm design — the same three-step shape (divide, conquer, combine) underlies sorting algorithms, fast multiplication, and a large share of the classic algorithms taught in any data structures course.

The three steps

Every divide-and-conquer algorithm follows the same structure:

  1. Divide — split the problem into two or more smaller subproblems of the same kind. Usually this means splitting an input in half, but the split doesn’t have to be even.
  2. Conquer — solve each subproblem recursively. If a subproblem is small enough to solve directly (the base case), solve it without further recursion.
  3. Combine — merge the subproblems’ solutions into a solution for the original, larger problem.

The recursion in step 2 is what makes this a strategy rather than a single algorithm: “divide and conquer” describes a shape you can pour many different problems into, not one specific procedure.

Classic example: merge sort

Merge sort is the textbook illustration of the pattern. To sort an array:

  1. Divide the array into two halves.
  2. Conquer by recursively sorting each half (down to the base case of a single-element array, which is trivially sorted).
  3. Combine the two sorted halves by merging them into one sorted array — walking both halves in order and repeatedly taking the smaller of the two current elements.
mergeSort(arr):
  if length(arr) <= 1: return arr
  mid = length(arr) / 2
  left  = mergeSort(arr[0:mid])
  right = mergeSort(arr[mid:])
  return merge(left, right)

The combine step — merging two already-sorted arrays into one — takes time proportional to their combined length, since it only needs a single pass through both. That linear-time merge, applied at every level of the recursion, is what gives merge sort its overall O(n log n) running time: log n levels of recursion, each doing O(n) total work across all the subproblems at that level.

Why splitting in half matters for running time

Divide and conquer’s efficiency usually comes from how the subproblem sizes shrink. Splitting a problem of size n into two problems of size n/2 produces a recursion tree with log₂ n levels — because halving a number repeatedly reaches 1 after roughly log₂ n halvings. If the work to divide and combine at each level is proportional to n, the total work across all log n levels is O(n log n), which is why merge sort, and divide-and-conquer algorithms generally, so often land at that particular running time. Contrast this with an algorithm that only shrinks the problem by a fixed amount each step (like removing one element at a time) — that produces n levels of recursion instead of log n, typically landing at O(n²) for comparable per-step work, exactly the running time big O notation uses to distinguish “scales fine” algorithms from ones that don’t.

Divide and conquer vs dynamic programming vs greedy

These three strategies are often taught together because they all attack problems by breaking them into smaller pieces, but they differ in whether those pieces overlap and how a final answer gets assembled:

Divide and conquerDynamic programmingGreedy
SubproblemsIndependent, non-overlappingOverlapping — solved once, reusedNot explicitly decomposed
Combines subresultsYes, explicitly (the “combine” step)Yes, via a recurrence over cached resultsNo — commits to one choice at each step
Typical useSorting, fast multiplication, closest-pair problemsOptimization problems with overlapping subproblems (e.g. shortest paths, edit distance)Problems where a locally optimal choice provably leads to a globally optimal one
Revisits earlier decisionsNoNo, but reuses their cached resultsNever

The distinction between greedy algorithms and dynamic programming is really about whether earlier subproblems overlap and need their results cached; divide and conquer sits apart from both because its subproblems are typically disjoint slices of the input, with nothing to cache between them. Dynamic programming is sometimes described as “divide and conquer with memoization,” which captures the overlap: both break a problem into smaller versions of itself, but DP specifically targets cases where those smaller versions repeat, and caching their answers avoids redundant recomputation.

Other well-known divide-and-conquer algorithms

  • Quicksort partitions an array around a pivot and recursively sorts the partitions — divide and conquer with the “combine” step folded almost entirely into the divide step, since a correctly partitioned array needs no further merging.
  • Binary search repeatedly halves a sorted array’s search space, discarding the half that can’t contain the target — arguably the simplest possible divide-and-conquer algorithm, since there’s only one subproblem to conquer at each step and no combine step at all.
  • The closest-pair-of-points problem finds the two closest points in a 2D plane by splitting the point set in half, recursively finding the closest pair in each half, and then checking a narrow strip near the dividing line for pairs that span both halves — solvable in O(n log n) instead of the O(n²) a naive all-pairs comparison would take.
  • Karatsuba multiplication multiplies large numbers faster than the standard grade-school algorithm by splitting each number into two halves and recursively combining three sub-multiplications instead of four, a divide-and-conquer trick that shaves the exponent in the running time.

When it’s the wrong tool

Divide and conquer assumes subproblems can be solved independently and recombined cheaply. When subproblems genuinely depend on shared state, or the same subproblem recurs many times across different branches of the recursion, a naive divide-and-conquer implementation ends up doing redundant work — recomputing the same subproblem repeatedly instead of reusing a cached answer, which is exactly the failure mode dynamic programming’s memoization is designed to avoid. Recognizing whether subproblems overlap is usually the deciding factor in choosing between the two approaches for a given problem.

The takeaway

Divide and conquer solves a problem by splitting it into smaller, independent subproblems, solving each recursively down to a simple base case, and combining the results back into a full solution. The pattern’s O(n log n) running time on problems like merge sort comes directly from that repeated halving — log n levels of recursion, each doing linear work. It’s the right tool when subproblems don’t overlap; when they do, dynamic programming’s caching is usually the better fit for the same divide-and-recurse shape.

The Lycoris Team The Lycoris Team · · 4 min read

The Rope Data Structure: Editing Huge Strings Efficiently

A rope is a binary tree of string chunks that makes inserting, deleting, and slicing huge strings fast, which is why text editors use it over plain arrays.

#Computer Science #Data Structures #Algorithms
The Lycoris Team The Lycoris Team · · 4 min read

Memoization vs. Tabulation in Dynamic Programming

Memoization caches results top-down via recursion; tabulation builds a table bottom-up with loops. Same technique, opposite direction, different tradeoffs.

#Computer Science #Algorithms #Data Structures
The Lycoris Team The Lycoris Team · · 4 min read

Manacher's Algorithm Explained

Manacher's algorithm finds the longest palindromic substring in linear time by reusing symmetry from palindromes already found, avoiding redundant checks.

#Computer Science #Algorithms #Data Structures