Ternary Search — Practice Tasks¶
15 graded practice problems with full Go / Java / Python solutions. Each task starts with a problem statement, gives constraints, then provides three implementations. Difficulty grows from "warmup" to "harder than the LC label suggests".
Task 1 — Ternary Search on a Bitonic Integer Array¶
Problem. Given an array a[] that first strictly increases then strictly decreases, return the index of the maximum.
Constraints. 1 <= n <= 10^5, all values distinct.
Go:
func ternaryPeak(a []int) int {
lo, hi := 0, len(a)-1
for hi-lo > 2 {
m1 := lo + (hi-lo)/3
m2 := hi - (hi-lo)/3
if a[m1] < a[m2] { lo = m1 + 1 } else { hi = m2 - 1 }
}
best := lo
for i := lo + 1; i <= hi; i++ {
if a[i] > a[best] { best = i }
}
return best
}
Java:
int ternaryPeak(int[] a) {
int lo = 0, hi = a.length - 1;
while (hi - lo > 2) {
int m1 = lo + (hi - lo) / 3;
int m2 = hi - (hi - lo) / 3;
if (a[m1] < a[m2]) lo = m1 + 1;
else hi = m2 - 1;
}
int best = lo;
for (int i = lo + 1; i <= hi; i++) if (a[i] > a[best]) best = i;
return best;
}
Python:
def ternary_peak(a):
lo, hi = 0, len(a) - 1
while hi - lo > 2:
m1 = lo + (hi - lo) // 3
m2 = hi - (hi - lo) // 3
if a[m1] < a[m2]: lo = m1 + 1
else: hi = m2 - 1
return max(range(lo, hi + 1), key=lambda i: a[i])
Task 2 — Recursive Variant¶
Problem. Same as Task 1, but recursive.
Go:
func ternaryPeakRec(a []int) int { return tprec(a, 0, len(a)-1) }
func tprec(a []int, lo, hi int) int {
if hi-lo <= 2 {
best := lo
for i := lo + 1; i <= hi; i++ { if a[i] > a[best] { best = i } }
return best
}
m1 := lo + (hi-lo)/3
m2 := hi - (hi-lo)/3
if a[m1] < a[m2] { return tprec(a, m1+1, hi) }
return tprec(a, lo, m2-1)
}
Java:
int ternaryPeakRec(int[] a) { return rec(a, 0, a.length - 1); }
int rec(int[] a, int lo, int hi) {
if (hi - lo <= 2) {
int best = lo;
for (int i = lo + 1; i <= hi; i++) if (a[i] > a[best]) best = i;
return best;
}
int m1 = lo + (hi - lo) / 3;
int m2 = hi - (hi - lo) / 3;
if (a[m1] < a[m2]) return rec(a, m1 + 1, hi);
return rec(a, lo, m2 - 1);
}
Python:
def ternary_peak_rec(a):
def rec(lo, hi):
if hi - lo <= 2:
return max(range(lo, hi + 1), key=lambda i: a[i])
m1 = lo + (hi - lo) // 3
m2 = hi - (hi - lo) // 3
if a[m1] < a[m2]: return rec(m1 + 1, hi)
return rec(lo, m2 - 1)
return rec(0, len(a) - 1)
Task 3 — Find the Maximum of a Real-Valued Unimodal Function¶
Problem. Find argmax_x f(x) on [lo, hi] to precision 1e-9.
Go:
func ternaryMaxFloat(lo, hi float64, f func(float64) float64) float64 {
for i := 0; i < 200; i++ {
m1 := lo + (hi-lo)/3
m2 := hi - (hi-lo)/3
if f(m1) < f(m2) { lo = m1 } else { hi = m2 }
}
return (lo + hi) / 2
}
Java:
double ternaryMaxFloat(double lo, double hi,
java.util.function.DoubleUnaryOperator f) {
for (int i = 0; i < 200; i++) {
double m1 = lo + (hi - lo) / 3.0;
double m2 = hi - (hi - lo) / 3.0;
if (f.applyAsDouble(m1) < f.applyAsDouble(m2)) lo = m1;
else hi = m2;
}
return (lo + hi) / 2;
}
Python:
def ternary_max_float(lo, hi, f):
for _ in range(200):
m1 = lo + (hi - lo) / 3
m2 = hi - (hi - lo) / 3
if f(m1) < f(m2): lo = m1
else: hi = m2
return (lo + hi) / 2
Task 4 — Find the Minimum of f(x) = x² - 6x + 5¶
Problem. True minimum is at x = 3 (calculus). Verify by ternary search.
Python:
def min_quadratic():
f = lambda x: x * x - 6 * x + 5
lo, hi = -100.0, 100.0
for _ in range(200):
m1 = lo + (hi - lo) / 3
m2 = hi - (hi - lo) / 3
if f(m1) > f(m2): lo = m1 # for min, flipped
else: hi = m2
return (lo + hi) / 2
Go:
func minQuadratic() float64 {
f := func(x float64) float64 { return x*x - 6*x + 5 }
lo, hi := -100.0, 100.0
for i := 0; i < 200; i++ {
m1 := lo + (hi-lo)/3
m2 := hi - (hi-lo)/3
if f(m1) > f(m2) { lo = m1 } else { hi = m2 }
}
return (lo + hi) / 2
}
Java:
double minQuadratic() {
java.util.function.DoubleUnaryOperator f = x -> x * x - 6 * x + 5;
double lo = -100, hi = 100;
for (int i = 0; i < 200; i++) {
double m1 = lo + (hi - lo) / 3.0;
double m2 = hi - (hi - lo) / 3.0;
if (f.applyAsDouble(m1) > f.applyAsDouble(m2)) lo = m1;
else hi = m2;
}
return (lo + hi) / 2;
}
Task 5 — Find Peak Element (LeetCode 162) — Ternary Variant¶
Problem. Given an array where a[i] != a[i+1], find any peak index (an i with a[i] > a[i-1] and a[i] > a[i+1], treating out-of-bounds as -∞).
Note: when the input is guaranteed bitonic, ternary applies. For the general LC162 (possibly multiple internal peaks), binary search via adjacent-pair comparison is the textbook answer.
Python (ternary, bitonic-input variant):
def findPeakElement(a):
lo, hi = 0, len(a) - 1
while hi - lo > 2:
m1 = lo + (hi - lo) // 3
m2 = hi - (hi - lo) // 3
if a[m1] < a[m2]:
lo = m1 + 1
else:
hi = m2 - 1
return max(range(lo, hi + 1), key=lambda i: a[i])
Go:
func findPeakElement(a []int) int {
lo, hi := 0, len(a)-1
for hi-lo > 2 {
m1 := lo + (hi-lo)/3
m2 := hi - (hi-lo)/3
if a[m1] < a[m2] { lo = m1 + 1 } else { hi = m2 - 1 }
}
best := lo
for i := lo + 1; i <= hi; i++ { if a[i] > a[best] { best = i } }
return best
}
Java:
int findPeakElement(int[] a) {
int lo = 0, hi = a.length - 1;
while (hi - lo > 2) {
int m1 = lo + (hi - lo) / 3;
int m2 = hi - (hi - lo) / 3;
if (a[m1] < a[m2]) lo = m1 + 1;
else hi = m2 - 1;
}
int best = lo;
for (int i = lo + 1; i <= hi; i++) if (a[i] > a[best]) best = i;
return best;
}
Task 6 — Closest Distance to a Parabola¶
Problem. Given (a, b), find the point on y = x² closest to it.
Python:
def closest_on_parabola(a, b):
def d2(t):
return (t - a) ** 2 + (t * t - b) ** 2
R = max(10.0, abs(a) + abs(b) ** 0.5 + 5)
lo, hi = -R, R
for _ in range(300):
m1 = lo + (hi - lo) / 3
m2 = hi - (hi - lo) / 3
if d2(m1) > d2(m2): lo = m1
else: hi = m2
t = (lo + hi) / 2
return (t, t * t)
Go:
import "math"
func closestOnParabola(a, b float64) (float64, float64) {
d2 := func(t float64) float64 {
return (t-a)*(t-a) + (t*t-b)*(t*t-b)
}
R := math.Max(10.0, math.Abs(a)+math.Sqrt(math.Max(0, b))+5)
lo, hi := -R, R
for i := 0; i < 300; i++ {
m1 := lo + (hi-lo)/3
m2 := hi - (hi-lo)/3
if d2(m1) > d2(m2) { lo = m1 } else { hi = m2 }
}
t := (lo + hi) / 2
return t, t * t
}
Java:
double[] closestOnParabola(double a, double b) {
java.util.function.DoubleUnaryOperator d2 = t ->
(t - a) * (t - a) + (t * t - b) * (t * t - b);
double R = Math.max(10.0, Math.abs(a) + Math.sqrt(Math.max(0, b)) + 5);
double lo = -R, hi = R;
for (int i = 0; i < 300; i++) {
double m1 = lo + (hi - lo) / 3.0;
double m2 = hi - (hi - lo) / 3.0;
if (d2.applyAsDouble(m1) > d2.applyAsDouble(m2)) lo = m1;
else hi = m2;
}
double t = (lo + hi) / 2;
return new double[]{t, t * t};
}
Task 7 — Minimize Time of Closest Approach¶
Problem. A car at position (x0, y0) moves with velocity (vx, vy). Find the time t* ≥ 0 minimizing distance to target (a, b).
Python:
def closest_time(x0, y0, vx, vy, a, b):
def d(t):
dx = (x0 + vx * t) - a
dy = (y0 + vy * t) - b
return (dx * dx + dy * dy) ** 0.5
lo, hi = 0.0, 1e9
for _ in range(300):
m1 = lo + (hi - lo) / 3
m2 = hi - (hi - lo) / 3
if d(m1) > d(m2): lo = m1
else: hi = m2
return (lo + hi) / 2
Go:
import "math"
func closestTime(x0, y0, vx, vy, a, b float64) float64 {
d := func(t float64) float64 {
dx := (x0 + vx*t) - a
dy := (y0 + vy*t) - b
return math.Sqrt(dx*dx + dy*dy)
}
lo, hi := 0.0, 1e9
for i := 0; i < 300; i++ {
m1 := lo + (hi-lo)/3
m2 := hi - (hi-lo)/3
if d(m1) > d(m2) { lo = m1 } else { hi = m2 }
}
return (lo + hi) / 2
}
Java:
double closestTime(double x0, double y0, double vx, double vy,
double a, double b) {
java.util.function.DoubleUnaryOperator d = t -> {
double dx = (x0 + vx * t) - a;
double dy = (y0 + vy * t) - b;
return Math.sqrt(dx * dx + dy * dy);
};
double lo = 0, hi = 1e9;
for (int i = 0; i < 300; i++) {
double m1 = lo + (hi - lo) / 3.0;
double m2 = hi - (hi - lo) / 3.0;
if (d.applyAsDouble(m1) > d.applyAsDouble(m2)) lo = m1;
else hi = m2;
}
return (lo + hi) / 2;
}
Task 8 — Optimal Projectile Angle¶
Problem. Given target horizontal distance D, find the launch angle θ ∈ (0, π/2) that minimizes the launch speed needed to land at D (vacuum model). Optimum should be π/4 = 45°.
Python:
import math
def optimal_angle(D):
g = 9.8
def speed_needed(theta):
s = math.sin(2 * theta)
if s <= 0: return float('inf')
return math.sqrt(D * g / s)
lo, hi = 1e-6, math.pi / 2 - 1e-6
for _ in range(200):
m1 = lo + (hi - lo) / 3
m2 = hi - (hi - lo) / 3
if speed_needed(m1) > speed_needed(m2): lo = m1
else: hi = m2
return (lo + hi) / 2 # Expected: π/4 ≈ 0.7854
Go:
import "math"
func optimalAngle(D float64) float64 {
g := 9.8
speedNeeded := func(theta float64) float64 {
s := math.Sin(2 * theta)
if s <= 0 { return math.Inf(1) }
return math.Sqrt(D * g / s)
}
lo, hi := 1e-6, math.Pi/2 - 1e-6
for i := 0; i < 200; i++ {
m1 := lo + (hi-lo)/3
m2 := hi - (hi-lo)/3
if speedNeeded(m1) > speedNeeded(m2) { lo = m1 } else { hi = m2 }
}
return (lo + hi) / 2
}
Java:
double optimalAngle(double D) {
double g = 9.8;
java.util.function.DoubleUnaryOperator speedNeeded = theta -> {
double s = Math.sin(2 * theta);
if (s <= 0) return Double.POSITIVE_INFINITY;
return Math.sqrt(D * g / s);
};
double lo = 1e-6, hi = Math.PI / 2 - 1e-6;
for (int i = 0; i < 200; i++) {
double m1 = lo + (hi - lo) / 3.0;
double m2 = hi - (hi - lo) / 3.0;
if (speedNeeded.applyAsDouble(m1) > speedNeeded.applyAsDouble(m2)) lo = m1;
else hi = m2;
}
return (lo + hi) / 2;
}
Task 9 — Maximize Area of an Inscribed Rectangle¶
Problem. Inside the parabola y = -x² + 4 and the x-axis, the largest axis-aligned rectangle has area A(x) = 2x × (4 - x²) for x ∈ [0, 2]. Find the x maximizing A.
Python:
def max_inscribed_rectangle():
A = lambda x: 2 * x * (4 - x * x)
lo, hi = 0.0, 2.0
for _ in range(200):
m1 = lo + (hi - lo) / 3
m2 = hi - (hi - lo) / 3
if A(m1) < A(m2): lo = m1
else: hi = m2
x = (lo + hi) / 2
return x, A(x) # Expected x ≈ 1.1547, area ≈ 6.158
Go:
func maxInscribedRect() (float64, float64) {
A := func(x float64) float64 { return 2 * x * (4 - x*x) }
lo, hi := 0.0, 2.0
for i := 0; i < 200; i++ {
m1 := lo + (hi-lo)/3
m2 := hi - (hi-lo)/3
if A(m1) < A(m2) { lo = m1 } else { hi = m2 }
}
x := (lo + hi) / 2
return x, A(x)
}
Java:
double[] maxInscribedRect() {
java.util.function.DoubleUnaryOperator A = x -> 2 * x * (4 - x * x);
double lo = 0, hi = 2;
for (int i = 0; i < 200; i++) {
double m1 = lo + (hi - lo) / 3.0;
double m2 = hi - (hi - lo) / 3.0;
if (A.applyAsDouble(m1) < A.applyAsDouble(m2)) lo = m1;
else hi = m2;
}
double x = (lo + hi) / 2;
return new double[]{x, A.applyAsDouble(x)};
}
Task 10 — Find in Mountain Array (LeetCode 1095)¶
Problem. Locate target in a mountain array. First find the peak (ternary), then binary-search each side.
Python:
def findInMountainArray(target, a):
# Step 1: peak
lo, hi = 0, len(a) - 1
while hi - lo > 2:
m1 = lo + (hi - lo) // 3
m2 = hi - (hi - lo) // 3
if a[m1] < a[m2]: lo = m1 + 1
else: hi = m2 - 1
peak = max(range(lo, hi + 1), key=lambda i: a[i])
# Step 2: ascending binary search [0, peak]
lo, hi = 0, peak
while lo <= hi:
m = (lo + hi) // 2
if a[m] == target: return m
if a[m] < target: lo = m + 1
else: hi = m - 1
# Step 3: descending binary search [peak, n-1]
lo, hi = peak, len(a) - 1
while lo <= hi:
m = (lo + hi) // 2
if a[m] == target: return m
if a[m] > target: lo = m + 1 # descending
else: hi = m - 1
return -1
Go:
func findInMountainArray(target int, a []int) int {
lo, hi := 0, len(a)-1
for hi-lo > 2 {
m1 := lo + (hi-lo)/3
m2 := hi - (hi-lo)/3
if a[m1] < a[m2] { lo = m1 + 1 } else { hi = m2 - 1 }
}
peak := lo
for i := lo + 1; i <= hi; i++ { if a[i] > a[peak] { peak = i } }
lo, hi = 0, peak
for lo <= hi {
m := lo + (hi-lo)/2
if a[m] == target { return m }
if a[m] < target { lo = m + 1 } else { hi = m - 1 }
}
lo, hi = peak, len(a)-1
for lo <= hi {
m := lo + (hi-lo)/2
if a[m] == target { return m }
if a[m] > target { lo = m + 1 } else { hi = m - 1 }
}
return -1
}
Java:
int findInMountainArray(int target, int[] a) {
int lo = 0, hi = a.length - 1;
while (hi - lo > 2) {
int m1 = lo + (hi - lo) / 3;
int m2 = hi - (hi - lo) / 3;
if (a[m1] < a[m2]) lo = m1 + 1;
else hi = m2 - 1;
}
int peak = lo;
for (int i = lo + 1; i <= hi; i++) if (a[i] > a[peak]) peak = i;
int l = 0, h = peak;
while (l <= h) {
int m = l + (h - l) / 2;
if (a[m] == target) return m;
if (a[m] < target) l = m + 1; else h = m - 1;
}
l = peak; h = a.length - 1;
while (l <= h) {
int m = l + (h - l) / 2;
if (a[m] == target) return m;
if (a[m] > target) l = m + 1; else h = m - 1;
}
return -1;
}
Task 11 — Golden-Section Search (Half the f Calls)¶
Problem. Implement golden-section search for argmax over [lo, hi] with precision eps. Should use one new function call per iteration after the first.
Python:
import math
def golden_section_max(lo, hi, f, eps=1e-9):
rho = (math.sqrt(5) - 1) / 2
m1 = hi - rho * (hi - lo)
m2 = lo + rho * (hi - lo)
f1, f2 = f(m1), f(m2)
while hi - lo > eps:
if f1 < f2:
lo = m1
m1, f1 = m2, f2
m2 = lo + rho * (hi - lo)
f2 = f(m2)
else:
hi = m2
m2, f2 = m1, f1
m1 = hi - rho * (hi - lo)
f1 = f(m1)
return (lo + hi) / 2
Go:
import "math"
func goldenSectionMax(lo, hi float64, f func(float64) float64, eps float64) float64 {
rho := (math.Sqrt(5) - 1) / 2
m1 := hi - rho*(hi-lo)
m2 := lo + rho*(hi-lo)
f1, f2 := f(m1), f(m2)
for hi-lo > eps {
if f1 < f2 {
lo = m1
m1, f1 = m2, f2
m2 = lo + rho*(hi-lo)
f2 = f(m2)
} else {
hi = m2
m2, f2 = m1, f1
m1 = hi - rho*(hi-lo)
f1 = f(m1)
}
}
return (lo + hi) / 2
}
Java:
double goldenSectionMax(double lo, double hi,
java.util.function.DoubleUnaryOperator f, double eps) {
double rho = (Math.sqrt(5) - 1) / 2.0;
double m1 = hi - rho * (hi - lo);
double m2 = lo + rho * (hi - lo);
double f1 = f.applyAsDouble(m1), f2 = f.applyAsDouble(m2);
while (hi - lo > eps) {
if (f1 < f2) {
lo = m1;
m1 = m2; f1 = f2;
m2 = lo + rho * (hi - lo); f2 = f.applyAsDouble(m2);
} else {
hi = m2;
m2 = m1; f2 = f1;
m1 = hi - rho * (hi - lo); f1 = f.applyAsDouble(m1);
}
}
return (lo + hi) / 2;
}
Task 12 — Maximum-Throughput Send Rate (Noise-Tolerant Variant)¶
Problem. A network's measured throughput is unimodal in send-rate but noisy. Find the optimal rate by averaging k = 5 samples per probe.
Python:
def best_send_rate_robust(measure_once, k=5):
"""measure_once(rate) returns one noisy throughput sample."""
def f(rate):
return sum(measure_once(rate) for _ in range(k)) / k
lo, hi = 1.0, 1e6
for _ in range(50): # fewer iters because each costs k samples
m1 = lo + (hi - lo) / 3
m2 = hi - (hi - lo) / 3
if f(m1) < f(m2): lo = m1
else: hi = m2
return (lo + hi) / 2
Go:
func bestSendRateRobust(measureOnce func(float64) float64, k int) float64 {
f := func(rate float64) float64 {
total := 0.0
for i := 0; i < k; i++ { total += measureOnce(rate) }
return total / float64(k)
}
lo, hi := 1.0, 1e6
for i := 0; i < 50; i++ {
m1 := lo + (hi-lo)/3
m2 := hi - (hi-lo)/3
if f(m1) < f(m2) { lo = m1 } else { hi = m2 }
}
return (lo + hi) / 2
}
Java:
double bestSendRateRobust(java.util.function.DoubleUnaryOperator measureOnce, int k) {
java.util.function.DoubleUnaryOperator f = rate -> {
double total = 0;
for (int i = 0; i < k; i++) total += measureOnce.applyAsDouble(rate);
return total / k;
};
double lo = 1, hi = 1e6;
for (int i = 0; i < 50; i++) {
double m1 = lo + (hi - lo) / 3.0;
double m2 = hi - (hi - lo) / 3.0;
if (f.applyAsDouble(m1) < f.applyAsDouble(m2)) lo = m1;
else hi = m2;
}
return (lo + hi) / 2;
}
Task 13 — Nested Ternary for 2-D Unimodal Surface¶
Problem. f(x, y) = -(x - 3)² - (y - 5)² + 100. Find argmax (x, y) over [0, 10] × [0, 10].
Python:
def nested_ternary_max():
f = lambda x, y: -(x - 3) ** 2 - (y - 5) ** 2 + 100
def best_y(x):
lo, hi = 0.0, 10.0
for _ in range(100):
m1 = lo + (hi - lo) / 3
m2 = hi - (hi - lo) / 3
if f(x, m1) < f(x, m2): lo = m1
else: hi = m2
return f(x, (lo + hi) / 2)
lo, hi = 0.0, 10.0
for _ in range(100):
m1 = lo + (hi - lo) / 3
m2 = hi - (hi - lo) / 3
if best_y(m1) < best_y(m2): lo = m1
else: hi = m2
return (lo + hi) / 2 # Expected x = 3 (and inner gives y = 5)
Go:
func nestedTernaryMax() float64 {
f := func(x, y float64) float64 { return -(x-3)*(x-3) - (y-5)*(y-5) + 100 }
bestY := func(x float64) float64 {
lo, hi := 0.0, 10.0
for i := 0; i < 100; i++ {
m1 := lo + (hi-lo)/3
m2 := hi - (hi-lo)/3
if f(x, m1) < f(x, m2) { lo = m1 } else { hi = m2 }
}
return f(x, (lo+hi)/2)
}
lo, hi := 0.0, 10.0
for i := 0; i < 100; i++ {
m1 := lo + (hi-lo)/3
m2 := hi - (hi-lo)/3
if bestY(m1) < bestY(m2) { lo = m1 } else { hi = m2 }
}
return (lo + hi) / 2
}
Java:
double nestedTernaryMax() {
java.util.function.DoubleBinaryOperator f =
(x, y) -> -(x - 3) * (x - 3) - (y - 5) * (y - 5) + 100;
java.util.function.DoubleUnaryOperator bestY = x -> {
double lo = 0, hi = 10;
for (int i = 0; i < 100; i++) {
double m1 = lo + (hi - lo) / 3.0;
double m2 = hi - (hi - lo) / 3.0;
if (f.applyAsDouble(x, m1) < f.applyAsDouble(x, m2)) lo = m1;
else hi = m2;
}
return f.applyAsDouble(x, (lo + hi) / 2);
};
double lo = 0, hi = 10;
for (int i = 0; i < 100; i++) {
double m1 = lo + (hi - lo) / 3.0;
double m2 = hi - (hi - lo) / 3.0;
if (bestY.applyAsDouble(m1) < bestY.applyAsDouble(m2)) lo = m1;
else hi = m2;
}
return (lo + hi) / 2;
}
Complexity: O(log² N) function evaluations.
Task 14 — Ladder Around a Corner¶
Problem. Two corridors of widths W and H meet at a right angle. The longest ladder that can be carried around the corner is min over θ ∈ (0, π/2) of (W / sin θ + H / cos θ). Find that minimum length.
Python:
import math
def longest_ladder(W, H):
def L(theta):
return W / math.sin(theta) + H / math.cos(theta)
lo, hi = 1e-6, math.pi / 2 - 1e-6
for _ in range(200):
m1 = lo + (hi - lo) / 3
m2 = hi - (hi - lo) / 3
if L(m1) > L(m2): lo = m1
else: hi = m2
return L((lo + hi) / 2)
Go:
import "math"
func longestLadder(W, H float64) float64 {
L := func(theta float64) float64 {
return W/math.Sin(theta) + H/math.Cos(theta)
}
lo, hi := 1e-6, math.Pi/2 - 1e-6
for i := 0; i < 200; i++ {
m1 := lo + (hi-lo)/3
m2 := hi - (hi-lo)/3
if L(m1) > L(m2) { lo = m1 } else { hi = m2 }
}
return L((lo + hi) / 2)
}
Java:
double longestLadder(double W, double H) {
java.util.function.DoubleUnaryOperator L = theta ->
W / Math.sin(theta) + H / Math.cos(theta);
double lo = 1e-6, hi = Math.PI / 2 - 1e-6;
for (int i = 0; i < 200; i++) {
double m1 = lo + (hi - lo) / 3.0;
double m2 = hi - (hi - lo) / 3.0;
if (L.applyAsDouble(m1) > L.applyAsDouble(m2)) lo = m1;
else hi = m2;
}
return L.applyAsDouble((lo + hi) / 2);
}
Aside: For W = H = 1, the answer is 2^(3/2) ≈ 2.828.
Task 15 — Minimize Maximum Distance (Parametric Ternary)¶
Problem. Given n points on the x-axis at positions p[i], place a new point x ∈ [min(p), max(p)] to minimize the maximum distance from x to any of the points (the 1-D 1-center problem).
Python:
def one_center_1d(points):
def max_dist(x):
return max(abs(x - p) for p in points)
lo, hi = min(points), max(points)
for _ in range(200):
m1 = lo + (hi - lo) / 3
m2 = hi - (hi - lo) / 3
if max_dist(m1) > max_dist(m2): lo = m1
else: hi = m2
return (lo + hi) / 2
Go:
import "math"
func oneCenter1D(points []float64) float64 {
maxDist := func(x float64) float64 {
m := 0.0
for _, p := range points {
d := math.Abs(x - p)
if d > m { m = d }
}
return m
}
lo, hi := points[0], points[0]
for _, p := range points {
if p < lo { lo = p }
if p > hi { hi = p }
}
for i := 0; i < 200; i++ {
m1 := lo + (hi-lo)/3
m2 := hi - (hi-lo)/3
if maxDist(m1) > maxDist(m2) { lo = m1 } else { hi = m2 }
}
return (lo + hi) / 2
}
Java:
double oneCenter1D(double[] points) {
double lo = points[0], hi = points[0];
for (double p : points) { lo = Math.min(lo, p); hi = Math.max(hi, p); }
final double[] pts = points;
java.util.function.DoubleUnaryOperator maxDist = x -> {
double m = 0;
for (double p : pts) m = Math.max(m, Math.abs(x - p));
return m;
};
for (int i = 0; i < 200; i++) {
double m1 = lo + (hi - lo) / 3.0;
double m2 = hi - (hi - lo) / 3.0;
if (maxDist.applyAsDouble(m1) > maxDist.applyAsDouble(m2)) lo = m1;
else hi = m2;
}
return (lo + hi) / 2;
}
Why ternary works: max_i |x - p[i]| is the maximum of convex functions, which is itself convex (hence unimodal). Its minimum is found by ternary in 200 iterations × n evaluations.
Self-Check¶
After completing these tasks, you should be able to:
- Write ternary search on an integer unimodal array from memory in any of Go / Java / Python.
- Write real-valued ternary search with a fixed iteration count.
- Implement golden-section search and explain why it makes half as many function calls.
- Apply ternary to parametric problems (closest approach, optimal angle, longest ladder).
- Recognize when ternary works (strict unimodality) vs when it fails (multimodal, noisy, monotonic).
- Use nested ternary for 2-D unimodal optimization.
- Choose between ternary and binary based on whether the data is unimodal or monotonic.
- Identify when to use derivative-based methods instead (when
f'(x)is available).
If any of these feel uncertain, revisit the relevant section of junior.md or middle.md and redo the task without looking at the solution.
In this topic
- interview
- tasks