Master ternary search — what it is, why it differs from binary search, how to find the maximum or minimum of a unimodal function in O(log n), ternary search on sorted arrays vs functions, comparison with binary search, and classic applications.
What Is Ternary Search?
Ternary search is a divide-and-conquer algorithm that finds the maximum or minimum of a unimodal function — a function that first increases (or decreases) monotonically, reaches a single peak (or valley), and then decreases (or increases) monotonically.
Unlike binary search which finds a specific value in a sorted array, ternary search finds the *extreme point* of a unimodal function. It divides the search range into three parts at each step.
The Ternary Search Algorithm
Divide the range into three equal parts using two midpoints m1 and m2:
| If f(m1) < f(m2): maximum is in [m1, right] | left = m1 |
| If f(m1) > f(m2): maximum is in [left, m2] | right = m2 |
| If f(m1) == f(m2): maximum is in [m1, m2] | shrink both ends |
Implementation — Find Maximum on Sorted Array
def ternary_search_max(arr):
"""
Find the index of the maximum element in a unimodal array.
Time: O(log n), Space: O(1)
"""
left, right = 0, len(arr) - 1
while right - left > 2:
m1 = left + (right - left) // 3
m2 = right - (right - left) // 3
if arr[m1] < arr[m2]:
left = m1 + 1 # Max is in right portion
elif arr[m1] > arr[m2]:
right = m2 - 1 # Max is in left portion
else:
left = m1 + 1 # Shrink from both sides
right = m2 - 1
# Find max in remaining small range
return max(range(left, right + 1), key=lambda i: arr[i])
arr = [1, 3, 5, 8, 10, 7, 4, 2]
print(ternary_search_max(arr)) # 4 (index of maximum 10)
Implementation — Find Maximum of Continuous Function
def ternary_search_continuous(f, left, right, precision=1e-9):
"""
Find x that maximizes f(x) on [left, right].
f must be unimodal (single peak).
Time: O(log((right-left)/precision)), Space: O(1)
"""
while right - left > precision:
m1 = left + (right - left) / 3
m2 = right - (right - left) / 3
if f(m1) < f(m2):
left = m1
elif f(m1) > f(m2):
right = m2
else:
left = m1
right = m2
return (left + right) / 2
# Find maximum of f(x) = -(x-3)² + 9 (parabola, peak at x=3)
f = lambda x: -(x - 3)**2 + 9
peak_x = ternary_search_continuous(f, 0, 6)
print(f"Peak at x ≈ {peak_x:.4f}, f(x) ≈ {f(peak_x):.4f}")
# Peak at x ≈ 3.0000, f(x) ≈ 9.0000
Ternary Search vs Binary Search
| Feature | Binary Search | Ternary Search |
|---|
| Purpose | Find specific value | Find maximum/minimum |
| Data requirement | Sorted array | Unimodal function |
| Steps per iteration | 1 comparison | 2 comparisons |
| Reduction per step | Eliminates 1/2 | Eliminates 1/3 |
| Complexity | O(log₂ n) | O(log₁.₅ n) ≈ O(log n) |
| When to use | Known sorted array | Unimodal optimization |
Why not divide into more parts? The more divisions, the more comparisons per step. Binary search with 1 comparison eliminating 1/2 is optimal for sorted array search. Ternary search makes 2 comparisons to eliminate 1/3 — the same total work per "elimination ratio" as binary search.
Classic Application — Optimal Point in Geometry
def closest_to_point_on_line(ax, ay, bx, by, px, py):
"""
Find point on segment AB closest to point P using ternary search.
The distance function d(t) for t in [0,1] is unimodal (convex).
"""
import math
def distance(t):
x = ax + t * (bx - ax)
y = ay + t * (by - ay)
return math.sqrt((x - px)**2 + (y - py)**2)
t = ternary_search_continuous(lambda t: -distance(t), 0, 1)
# We minimize distance = maximize negative distance
x = ax + t * (bx - ax)
y = ay + t * (by - ay)
return x, y
Ternary Search in Competitive Programming
Common CP problems using ternary search:
1. "Find x ∈ [L,R] that minimizes/maximizes f(x)" where f is convex/concave
2. "Find the peak in a bitonic array" (also solvable with binary search)
3. "Optimal position on a line to minimize total distance to N points"
4. "Largest rectangle area in histogram" (ternary not needed, but shows unimodal)
# Competitive programming template for ternary search
def solve():
L, R = 0, 10**18 # Search range (integers)
EPS = 100 # Precision (stop when range <= EPS)
while R - L > EPS:
m1 = L + (R - L) // 3
m2 = R - (R - L) // 3
if f(m1) <= f(m2): # For minimum, use <=
L = m1 + 1
else:
R = m2 - 1
# Check all values in [L, R]
return min(f(x) for x in range(L, R + 1))
Number of Iterations
Ternary search reduces the range to 2/3 each step:
After k iterations: range = (2/3)^k × original_range
To reach precision ε from range R:
(2/3)^k × R = ε
k = log(R/ε) / log(3/2) ≈ 2.41 × log₂(R/ε)
For integers [0, 10^9]:
k ≈ 2.41 × log₂(10⁹) ≈ 2.41 × 30 ≈ 72 iterations
Summary
- Ternary search finds the maximum or minimum of a unimodal function
- Divides range into thirds using two probes m1 and m2
- Eliminates one-third of the range per iteration → O(log n) total
- Requires: Strictly unimodal function on the search range
- Use when: Optimizing over a continuous or discrete domain with a single peak/valley
- For sorted array value search → use binary search instead
*Next Lesson: Search Comparison*