Skip to content
Home
Divide and Conquer: Breaking Problems Down

Divide and Conquer: Breaking Problems Down

Data Structures Data Structures 7 min read 1480 words Beginner ExcellentWiki Editorial Team

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

The Three Steps

Divide  → Split problem into smaller subproblems
Conquer → Solve subproblems recursively (base case: trivial to solve)
Combine → Merge subproblem solutions into final solution

The Master Theorem

Analyzes recurrence relations of the form:

T(n) = aT(n/b) + f(n)

Where:

  • a = number of subproblems
  • b = factor by which input size is reduced
  • f(n) = cost of dividing and combining

Three Cases

Case 1: If f(n) = O(n^c) where c < log_b(a) → T(n) = Θ(n^log_b(a))
Case 2: If f(n) = Θ(n^c) where c = log_b(a) → T(n) = Θ(n^c log n)
Case 3: If f(n) = Ω(n^c) where c > log_b(a) → T(n) = Θ(f(n))

Examples

AlgorithmRecurrenceComplexity
Binary searchT(n) = T(n/2) + O(1)O(log n)
Merge sortT(n) = 2T(n/2) + O(n)O(n log n)
Quick sort (avg)T(n) = 2T(n/2) + O(n)O(n log n)
Quick sort (worst)T(n) = T(n-1) + O(n)O(n²)
Karatsuba (multiplication)T(n) = 3T(n/2) + O(n)O(n^1.58)
Strassen (matrix multiply)T(n) = 7T(n/2) + O(n²)O(n^2.81)

Classic Divide and Conquer Algorithms

Merge Sort

def mergesort(arr):
    if len(arr) <= 1:
        return arr
    mid = len(arr) // 2
    left = mergesort(arr[:mid])
    right = mergesort(arr[mid:])
    return merge(left, right)

def merge(left, right):
    result = []
    i = j = 0
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    return result + left[i:] + right[j:]

Divide: Split array in half. O(1) Conquer: Sort halves recursively. 2T(n/2) Combine: Merge sorted halves. O(n)

Quick Sort

def quicksort(arr, low, high):
    if low < high:
        pivot = partition(arr, low, high)
        quicksort(arr, low, pivot - 1)
        quicksort(arr, pivot + 1, high)

def partition(arr, low, high):
    pivot = arr[high]
    i = low - 1
    for j in range(low, high):
        if arr[j] <= pivot:
            i += 1
            arr[i], arr[j] = arr[j], arr[i]
    arr[i + 1], arr[high] = arr[high], arr[i + 1]
    return i + 1

Divide: Partition around pivot. O(n) Conquer: Sort partitions recursively. Combine: Trivial (in-place).

Binary Search

def binary_search(arr, target, low, high):
    if low > high:
        return -1
    mid = (low + high) // 2
    if arr[mid] == target:
        return mid
    elif arr[mid] < target:
        return binary_search(arr, target, mid + 1, high)
    else:
        return binary_search(arr, target, low, mid - 1)

Divide: Compare with middle element. O(1) Conquer: Search one half. T(n/2) Combine: Trivial.

Closest Pair of Points

Find the minimum distance between any two points in a plane.

def closest_pair(points):
    if len(points) <= 3:
        return brute_force(points)
    mid = len(points) // 2
    mid_x = points[mid][0]
    left = closest_pair(points[:mid])
    right = closest_pair(points[mid:])
    d = min(left, right)
    strip = [p for p in points if abs(p[0] - mid_x) < d]
    strip.sort(key=lambda p: p[1])
    for i in range(len(strip)):
        for j in range(i + 1, min(i + 7, len(strip))):
            d = min(d, distance(strip[i], strip[j]))
    return d

Divide: Split points by x-coordinate. O(1) Conquer: Find closest in each half. 2T(n/2) Combine: Check points near dividing line. O(n)

Time: O(n log n)

Divide and Conquer vs. Dynamic Programming

| Aspect | Divide and Conquer | Dynamic Programming | |

Optimizing Divide and Conquer

Reducing Subproblems

Karatsuba multiplication: 3 multiplications instead of 4 → O(n^1.58) instead of O(n²):

def karatsuba(x, y):
    if x < 10 or y < 10:
        return x * y
    n = max(len(str(x)), len(str(y)))
    m = n // 2
    a, b = divmod(x, 10**m)
    c, d = divmod(y, 10**m)
    ac = karatsuba(a, c)
    bd = karatsuba(b, d)
    ad_bc = karatsuba(a + b, c + d) - ac - bd
    return ac * 10**(2*m) + ad_bc * 10**m + bd

Reducing Combine Cost

Quick sort combines in O(1) (in-place), while merge sort combines in O(n). But merge sort gives guaranteed O(n log n).

Common Mistakes

  • Not handling base cases: Must reach a trivially solvable case
  • Incorrect combination: Combining is harder than it looks (closest pair strip check)
  • Assuming non-overlapping: If subproblems overlap, DP is better
  • Stack overflow: Recursive depth can exceed stack for large n (use iteration)

Divide and conquer is a fundamental algorithm design paradigm. Master it by understanding how to split problems and combine solutions.

Core Divide-and-Conquer Techniques

Recurrence Relations and Analysis

Divide-and-conquer algorithms are analyzed using recurrence relations. For an algorithm that divides a problem of size n into a subproblems of size n/b and requires f(n) work to divide and combine, the recurrence is T(n) = a × T(n/b) + f(n). The Master Theorem provides closed-form solutions for common recurrences. Merge sort with T(n) = 2T(n/2) + O(n) solves to O(n log n), while binary search with T(n) = T(n/2) + O(1) solves to O(log n). The median-of-medians algorithm for finding the kth smallest element in worst-case O(n) uses a clever divide step that partitions the input into groups of 5. Understanding recurrence structure helps identify whether a problem is amenable to divide-and-conquer decomposition.

Classic Divide-and-Conquer Examples

Merge sort divides an array into two halves, recursively sorts each half, and merges the sorted halves. The merging step is where the real work happens — comparing elements from both halves and placing the smaller element into the output. Quicksort selects a pivot, partitions the array around it, and recursively sorts the subarrays on each side. Unlike merge sort, quicksort’s divide step does the work and the conquer step is trivial. Strassen’s matrix multiplication reduces the standard 8 recursive multiplications to 7 by cleverly combining submatrix products, achieving O(n^2.807) complexity compared to the naive O(n³). The fast Fourier transform (FFT) recursively divides a polynomial into even and odd coefficients, achieving O(n log n) time for convolution operations.

Applying Divide and Conquer

Identifying opportunities for divide-and-conquer requires recognizing when a problem can be broken into independent subproblems. The subproblems must be smaller instances of the same problem, and the solution must be composable from subproblem solutions. Common indicators include pairs problems (count inversions, closest pair), tree-based problems (where the data structure itself suggests recursive decomposition), and range queries (segment trees and Fenwick trees). In practice, divide-and-conquer algorithms are often combined with other strategies — dynamic programming uses divide-and-conquer at each state transition, and greedy algorithms sometimes employ divide-and-conquer for the subproblem structure.

Parallel Divide-and-Conquer

Divide-and-conquer algorithms are naturally parallelizable because subproblems are independent. Parallel merge sort distributes subarrays across processors, merges local results, and then performs a hierarchical merge. MapReduce frameworks apply divide-and-conquer at massive scale — the map phase divides input data into independent chunks for parallel processing, and the reduce phase combines results. The parallel prefix sum (scan) algorithm uses a divide-and-conquer tree to compute prefix sums in O(log n) parallel time. Fork-join parallelism models divide-and-conquer directly — a fork creates parallel tasks for subproblems, and join combines results. This model is supported by frameworks like Java’s ForkJoinPool and C++’s TBB. The key performance consideration is granularity: splitting into too many tiny subproblems creates overhead from task creation and scheduling.

Divide-and-Conquer in Computational Geometry

The closest pair of points problem demonstrates elegant divide-and-conquer geometry. The line dividing points into left and right halves is solved recursively, then the minimal distance d from both halves is found. A strip of width d around the dividing line is examined — points within the strip are sorted by y-coordinate, and each point is compared with at most the next 7 points in the sorted order, yielding O(n log n) total time. The convex hull problem also admits a divide-and-conquer solution — recursively compute the convex hull of left and right halves, then merge by finding upper and lower supporting lines in linear time. The Delaunay triangulation can be constructed in O(n log n) time using divide-and-conquer by recursively triangulating halves and merging along a dividing line, removing triangles that violate the empty circumcircle property.

Frequently Asked Questions

When should I use divide and conquer over other strategies? Use divide and conquer when the problem naturally decomposes into independent subproblems whose solutions can be combined efficiently. If subproblems overlap significantly, dynamic programming is more appropriate. If a greedy choice at each step leads to optimal solution, use greedy algorithms. Divide and conquer excels for sorting, searching, matrix operations, and computational geometry.

What is the difference between divide and conquer and dynamic programming? Divide and conquer solves independent subproblems that do not overlap. Each subproblem is solved once. Dynamic programming solves overlapping subproblems — the same subproblem appears multiple times, and DP stores the result to avoid recomputation. Both use recursion, but DP adds memoization for efficiency.

How do I analyze the time complexity of divide-and-conquer algorithms? Use the Master Theorem for standard recurrences. Write the recurrence as T(n) = aT(n/b) + O(n^d). Compare log_b(a) to d: if log_b(a) > d, complexity is O(n^{log_b(a)}); if equal, O(n^d log n); if less, O(n^d). For non-standard recurrences, use recursion tree analysis or the Akra-Bazzi method.

Sorting Algorithms GuideSearching Algorithms GuideDynamic Programming Guide

Section: Data Structures 1480 words 7 min read Beginner 756 articles in section Report inaccuracy Back to top