Algorithms Foundations
10 min read

K-Crystal Balls Problem: Jump Search Pattern

The K-crystal balls (or K-egg drop) problem demonstrates how constrained resources fundamentally change optimal search strategy. With unlimited test resources, binary search achieves O(log n). With exactly k resources that are consumed on failure, the optimal worst-case complexity becomes O(n^(1/k))—a jump search pattern where each resource enables one level of hierarchical partitioning.

Strategy Evolution by Resource Count

Add resource

Add resource

...

k=∞: Binary Search

Halve repeatedly → log n

k=1: Linear Only

1→2→3→...→n

k=2: Jump + Linear

Jump √n

Linear √n

k=3: Jump² + Jump + Linear

Jump n^(2/3)

Jump n^(1/3)

Linear n^(1/3)

Resource count determines search depth: each additional resource enables one more level of hierarchical partitioning, reducing worst-case from O(n) toward O(log n).

The K-crystal balls problem models threshold detection with consumable test resources—a pattern appearing in software version bisection, network MTU discovery, and reliability testing.

Core insight: When resources are limited, balance work across all phases by making each phase contribute equally to worst-case cost. For k resources and n positions:

  • Jump size at phase i: n(ki)/kn^{(k-i)/k}
  • Worst-case operations: kn1/kk \cdot n^{1/k}
  • Complexity: O(n1/k)O(n^{1/k}) for fixed k

The formula emerges from setting all phase costs equal and solving the resulting geometric sequence. This isn’t arbitrary—it’s the unique configuration where no phase can be improved without making another worse.

Resources (k)Jump PatternWorst CaseComplexity
1Linear onlynO(n)
2√n → linear2√nO(√n)
3n^(2/3) → n^(1/3) → linear3·∛nO(∛n)
kn^((k-1)/k) → … → n^(1/k) → 1k·n^(1/k)O(n^(1/k))
log n2 → 2 → …2·log nO(log n)

Given k identical crystal balls and a structure with n floors, find the exact threshold floor where balls start breaking using minimum drops in the worst case.

Formal model:

  • Floors form a sorted boolean array: [false, false, ..., false, true, true, ..., true]
  • All floors below threshold are safe (false); threshold and above break (true)
  • A broken ball is permanently consumed
  • Objective: Minimize maximum drops across all possible threshold positions

Why worst-case matters: In production systems, you often can’t afford to rely on average-case luck. The worst-case bound guarantees performance regardless of where the threshold lies.

Binary search seems natural—it’s optimal for searching sorted data. With k=2 balls and n=100 floors:

  1. Drop from floor 50
  2. If it breaks, you have 1 ball left and must linear search floors 1-49
  3. Worst case: 1 + 49 = 50 drops

The problem: binary search assumes tests are non-destructive. When a test consumes the resource on failure, the search space constraints change asymmetrically. After a break, you lose both the ball and the ability to take risks.

This asymmetry is why the optimal strategy for k=2 isn’t “binary then linear” (worst case ~n/2) but “jump √n then linear” (worst case 2√n). The sqrt improvement comes from properly accounting for resource consumption.

With one ball, any break ends the search without finding the threshold. The only safe strategy is linear search.

Strategy: Test floors 1, 2, 3, …, n sequentially

Worst case: n drops (threshold is at floor n)

Complexity: O(n)

This establishes the baseline—with zero tolerance for resource loss, you cannot exploit structure.

With two balls, you can afford one “exploratory” break before falling back to linear search.

Strategy:

  1. Jump by interval j with the first ball
  2. When it breaks at some floor, linear search the previous j floors with the second ball

Analysis:

  • Number of jumps before break: at most ⌈n/j⌉
  • Linear search after break: at most j-1 steps
  • Total worst case: n/j + j (approximately)

Optimization: Minimize f(j) = n/j + j

Taking the derivative: f(j)=nj2+1=0f'(j) = -\frac{n}{j^2} + 1 = 0

Solving: j2=n    j=nj^2 = n \implies j = \sqrt{n}

Second derivative confirms minimum: f(j)=2nj3>0f''(j) = \frac{2n}{j^3} > 0

Worst case: nn+n=2n\frac{n}{\sqrt{n}} + \sqrt{n} = 2\sqrt{n}

Complexity: O(√n)

With three balls, you can partition twice before linear search.

Strategy:

  1. Jump by j₁ with the first ball
  2. Within the broken segment, jump by j₂ with the second ball
  3. Linear search the final segment with the third ball

Analysis:

  • Phase 1: n/j₁ jumps
  • Phase 2: j₁/j₂ jumps
  • Phase 3: j₂ linear steps
  • Total: n/j₁ + j₁/j₂ + j₂

Optimization: To minimize the maximum of these terms (worst case), make them equal.

Set nj1=j1j2=j2=x\frac{n}{j_1} = \frac{j_1}{j_2} = j_2 = x

Working backwards:

  • j2=xj_2 = x
  • j1=xj2=x2j_1 = x \cdot j_2 = x^2
  • n=xj1=xx2=x3n = x \cdot j_1 = x \cdot x^2 = x^3

Therefore: x=n1/3x = n^{1/3}

Jump sizes:

  • j1=x2=n2/3j_1 = x^2 = n^{2/3}
  • j2=x=n1/3j_2 = x = n^{1/3}

Worst case: 3n1/33 \cdot n^{1/3}

Complexity: O(n^(1/3))

With k resources, create k phases of decreasing granularity:

  1. Phase 1: Jump by j1j_1 until first resource consumed
  2. Phase 2: Within that segment, jump by j2j_2 until second consumed
  3. Phase k: Linear search (jump by 1)

Total=nj1+j1j2+j2j3++jk1\text{Total} = \frac{n}{j_1} + \frac{j_1}{j_2} + \frac{j_2}{j_3} + \cdots + j_{k-1}

Where jk=1j_k = 1 (linear search in final phase).

Consider any configuration where terms are unequal. The largest term determines the worst case. By reducing the largest term (larger jumps) and increasing smaller terms (smaller jumps), we can improve overall worst case until all terms equalize.

Formally: for a fixed product of terms (constrained by n), the sum is minimized when all terms are equal (AM-GM inequality application).

Set each term equal to x: nj1=j1j2=j2j3==jk1=x\frac{n}{j_1} = \frac{j_1}{j_2} = \frac{j_2}{j_3} = \cdots = j_{k-1} = x

Working backwards from jk1=xj_{k-1} = x:

jk1=xj_{k-1} = x jk2=xjk1=x2j_{k-2} = x \cdot j_{k-1} = x^2 jk3=xjk2=x3j_{k-3} = x \cdot j_{k-2} = x^3 \vdots j1=xk1j_1 = x^{k-1}

From the first equation: nj1=x    n=xj1=xxk1=xk\frac{n}{j_1} = x \implies n = x \cdot j_1 = x \cdot x^{k-1} = x^k

Solution: x=n1/kx = n^{1/k}

For phase i (1 ≤ i ≤ k-1): ji=n(ki)/kj_i = n^{(k-i)/k}

This creates a geometric sequence of jump sizes, each smaller than the previous by factor n1/kn^{1/k}.

Worst-case operations: kn1/kk \cdot n^{1/k}

Time complexity: O(k · n^(1/k))

For fixed k, this simplifies to O(n^(1/k)).

The solution emerges from a single insight: balance work across phases. Any imbalance means one phase dominates worst-case cost, and that phase could be improved by stealing resources from cheaper phases.

This is the minimax principle in action—minimizing the maximum cost across all phases.

An alternative approach uses decreasing jumps (x, x-1, x-2, …) to account for “wasted” successful drops. This yields slightly better constants:

For k=2 and n=100:

  • Fixed √n jumps: worst case ≈ 20 drops
  • Decreasing jumps (14, 13, 12, …): worst case = 14 drops

The decreasing approach satisfies x(x+1)2n\frac{x(x+1)}{2} \geq n, giving x=1+1+8n2x = \lceil\frac{-1 + \sqrt{1+8n}}{2}\rceil.

However, the fixed jump approach is simpler, has the same asymptotic complexity, and generalizes cleanly to k > 2.

Each additional resource reduces the exponent by 1/k(k+1):

Resourcesn = 1,000,000Improvement Factor
11,000,000
22,000500×
33006.7×
41262.4×
5791.6×

The first few resources provide massive gains; subsequent ones help less. This matches intuition—once you can partition sufficiently, additional partitioning levels add diminishing value.

When k = log₂(n), the jump size becomes approximately 2, and you get near-binary-search behavior.

With unlimited resources (k → ∞), pure binary search is optimal: O(log n).

The K-crystal balls problem thus interpolates between:

  • k=1: Linear search, no structure exploitation
  • k=∞: Binary search, maximum structure exploitation
k-crystal-balls.ts
5 collapsed lines
/**
* K-Crystal Balls: Jump search with limited test resources
* Time: O(k * n^(1/k)) where k = number of balls
* Space: O(1)
*/
function findBreakingFloor(floors: boolean[], k: number): number {
const n = floors.length
if (n === 0) return -1
if (k === 0) return -1 // No resources to test
// k=1: Must use linear search
if (k === 1) {
for (let i = 0; i < n; i++) {
if (floors[i]) return i
}
return -1
}
// General case: Jump by n^(1/k), recurse with k-1 resources
const jumpSize = Math.max(1, Math.floor(Math.pow(n, (k - 1) / k)))
let lastSafe = 0
let position = jumpSize
// Phase 1: Jump search with first resource
while (position < n && !floors[position]) {
lastSafe = position
position += jumpSize
}
// Found segment [lastSafe, min(position, n-1)] containing threshold
// Recursively search with k-1 resources
const segmentStart = lastSafe
const segmentEnd = Math.min(position, n - 1)
const segment = floors.slice(segmentStart, segmentEnd + 1)
const relativeIndex = findBreakingFloor(segment, k - 1)
12 collapsed lines
return relativeIndex === -1 ? -1 : segmentStart + relativeIndex
}
// Iterative version for k=2 (most common case)
function twoEggDrop(floors: boolean[]): number {
const n = floors.length
const jump = Math.floor(Math.sqrt(n))
let lastSafe = 0
let pos = 0
// Jump phase
while (pos < n && !floors[pos]) {
lastSafe = pos
pos += jump
}
// Linear phase
for (let i = lastSafe; i < Math.min(pos + 1, n); i++) {
if (floors[i]) return i
}
return -1
}
ConditionBehavior
k = 0Cannot determine threshold (no test resources)
k > log₂(n)Binary search suffices; extra resources unused
n = 0No floors to test; return -1
n = 1Single test determines threshold
All falseNo breaking floor exists; return -1
All trueThreshold at floor 0
Threshold at floor 1First jump overshoots; linear search finds it

For very large n, n1/kn^{1/k} may have floating-point errors. Use integer-safe computation:

// Avoid: Math.pow(n, 1/k) has precision issues for large n
// Prefer: Newton's method or integer nth-root algorithms

The jump search must handle:

  • Jumping past the array bounds (clamp to n-1)
  • Segment boundaries (inclusive vs exclusive)
  • The case where threshold is exactly at a jump position

Problem: A bug exists in version V but not version V-100. Find the introducing commit with minimal builds.

This is k-crystal-balls with k = number of parallel build machines. With 2 machines, jump by √100 ≈ 10 versions, then linear search the breaking segment.

Problem: Find the maximum transmission unit (MTU) for a network path without excessive packet loss.

Packets that exceed MTU are dropped (the “ball breaks”). With limited retransmission budget, jump search finds optimal MTU faster than linear probing.

Problem: Find the load threshold where a system fails, using limited destructive test runs.

Each test that causes failure consumes resources (hardware, time, budget). The K-crystal balls strategy minimizes tests needed to bracket the threshold.

Problem: Find the selectivity threshold where an index becomes beneficial, with limited benchmark runs.

Each benchmark consumes time. Jump search with k benchmark “resources” finds the crossover point efficiently.

  • Basic calculus (derivatives for optimization)
  • Understanding of time complexity notation
  • Familiarity with binary search and its assumptions
  • Threshold floor: The lowest floor where balls break (the target to find)
  • Worst case: Maximum operations across all possible threshold positions
  • Jump search: Search strategy using fixed-size jumps followed by linear scan
  • Consumable resource: A test resource that is destroyed on certain outcomes
  • K-crystal balls models threshold detection with limited destructive tests
  • Optimal jump size at phase i is n(ki)/kn^{(k-i)/k}, derived from equalizing phase costs
  • Worst-case complexity is O(n^(1/k)) for fixed k
  • k=1 gives O(n); k=∞ gives O(log n); intermediate k interpolates between these
  • Applications include version bisection, MTU discovery, and reliability testing
  • The solution demonstrates the minimax principle: balance work to minimize maximum cost

Read more