Introduction
Divide and conquer is an algorithm design paradigm that solves a problem by breaking it into smaller subproblems of the same type, solving each subproblem recursively, and then combining their solutions to solve the original problem. It powers some of the most important algorithms in computer science, including merge sort, quicksort, binary search, and fast Fourier transforms.
Cricket analogy: Analyzing a whole innings by splitting it into powerplay, middle overs, and death overs, solving each phase's strategy separately, then combining them into a full match plan is divide and conquer applied to cricket strategy.
Algorithm/Syntax
def divide_and_conquer(problem):
# Base case: problem is small enough to solve directly
if is_base_case(problem):
return solve_directly(problem)
# Divide: split the problem into smaller subproblems
subproblems = divide(problem)
# Conquer: solve each subproblem recursively
sub_solutions = [divide_and_conquer(sp) for sp in subproblems]
# Combine: merge subproblem solutions into the final solution
return combine(sub_solutions)Explanation
Every divide-and-conquer algorithm follows three steps. Divide splits the input into two or more smaller subproblems that resemble the original problem. Conquer solves each subproblem recursively; when a subproblem is small enough, it is solved directly (the base case). Combine merges the subproblem solutions into a solution for the original problem. The efficiency of a divide-and-conquer algorithm depends on how the work of dividing and combining compares to the size reduction achieved at each level of recursion, which is why recurrence relations and the master theorem are used to analyze running time.
Cricket analogy: Divide splits a run chase into overs; conquer solves each over's target recursively until a single ball is the base case; combine adds up the runs needed across overs into the full chase target, with net run rate math resembling a recurrence relation.
Example
def max_subarray_sum(arr, lo, hi):
"""Divide and conquer solution to the maximum subarray sum problem."""
if lo == hi:
return arr[lo]
mid = (lo + hi) // 2
left_sum = max_subarray_sum(arr, lo, mid)
right_sum = max_subarray_sum(arr, mid + 1, hi)
cross_sum = max_crossing_sum(arr, lo, mid, hi)
return max(left_sum, right_sum, cross_sum)
def max_crossing_sum(arr, lo, mid, hi):
left_best = float('-inf')
total = 0
for i in range(mid, lo - 1, -1):
total += arr[i]
left_best = max(left_best, total)
right_best = float('-inf')
total = 0
for i in range(mid + 1, hi + 1):
total += arr[i]
right_best = max(right_best, total)
return left_best + right_best
nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
print(max_subarray_sum(nums, 0, len(nums) - 1)) # 6, from subarray [4, -1, 2, 1]Complexity
The running time of a divide-and-conquer algorithm is typically expressed with a recurrence T(n) = a*T(n/b) + f(n), where a is the number of subproblems, n/b is their size, and f(n) is the cost of dividing and combining. The maximum subarray example divides into 2 subproblems of half the size with O(n) combine work, giving T(n) = 2T(n/2) + O(n), which the master theorem resolves to O(n log n). Different combine costs yield different results: binary search has T(n) = T(n/2) + O(1), giving O(log n).
Cricket analogy: A run-chase model that splits an innings into 2 halves of overs, each needing O(n) work to adjust the required rate, gives T(n) = 2T(n/2) + O(n), resolving to O(n log n) by the master theorem, just like the maximum subarray algorithm.
Key Takeaways
- Divide and conquer breaks a problem into smaller subproblems of the same type, solves them recursively, and combines the results.
- The three canonical steps are divide, conquer, and combine, with a base case that stops the recursion.
- Recurrence relations of the form T(n) = a*T(n/b) + f(n) describe the running time and are solved using the master theorem or recursion trees.
- Classic examples include merge sort, quicksort, binary search, quickselect, and the closest pair of points algorithm.
Practice what you learned
1. What are the three canonical steps of the divide-and-conquer paradigm?
2. Which recurrence describes the maximum subarray sum algorithm shown in this lesson?
3. What role does the base case play in a divide-and-conquer algorithm?
4. Which of the following is NOT typically classified as a divide-and-conquer algorithm?
Was this page helpful?
You May Also Like
Merge Sort as Divide and Conquer
See how merge sort applies divide, conquer, and combine to achieve guaranteed O(n log n) sorting.
The Quickselect Algorithm
Find the k-th smallest element in linear average time using a partition-based divide-and-conquer approach.
The Master Theorem
Apply the Master Theorem's three cases to quickly solve divide-and-conquer recurrences of the form T(n) = aT(n/b) + f(n).
Recursion Trees and Recurrence Relations
Model the runtime of recursive algorithms as recurrence relations and solve them by drawing recursion trees.