1074-egg-drop — Egg Drop
Tutorial
The Problem
Given k eggs and n floors, find the minimum number of trials needed to determine the critical floor above which eggs break. This is a classic DP/binary-search problem that appears in reliability testing, threshold determination, and A/B test stopping rules.
The naive O(kn²) DP can be improved to O(kn log n) using binary search, and further to O(kn) with a rolling-minimum approach.
🎯 Learning Outcomes
Code Example
#![allow(clippy::all)]
// 1074: Egg Drop — DP + Binary Search
// Approach 1: Basic DP O(k*n^2)
fn egg_drop_basic(eggs: usize, floors: usize) -> usize {
let mut dp = vec![vec![0usize; floors + 1]; eggs + 1];
for i in 1..=eggs {
for j in 1..=floors {
if i == 1 {
dp[i][j] = j;
} else {
dp[i][j] = usize::MAX;
for x in 1..=j {
let worst = 1 + dp[i - 1][x - 1].max(dp[i][j - x]);
dp[i][j] = dp[i][j].min(worst);
}
}
}
}
dp[eggs][floors]
}
// Approach 2: DP with binary search O(k*n*log(n))
fn egg_drop_bs(eggs: usize, floors: usize) -> usize {
let mut dp = vec![vec![0usize; floors + 1]; eggs + 1];
for i in 1..=eggs {
for j in 1..=floors {
if i == 1 {
dp[i][j] = j;
} else {
let (mut lo, mut hi) = (1, j);
while lo < hi {
let mid = (lo + hi) / 2;
if dp[i - 1][mid - 1] < dp[i][j - mid] {
lo = mid + 1;
} else {
hi = mid;
}
}
dp[i][j] = 1 + dp[i - 1][lo - 1].max(dp[i][j - lo]);
}
}
}
dp[eggs][floors]
}
// Approach 3: Optimal — how many floors can we check with t trials and k eggs?
fn egg_drop_optimal(eggs: usize, floors: usize) -> usize {
// dp[t][k] = max floors checkable with t trials and k eggs
let mut dp = vec![vec![0usize; eggs + 1]; floors + 1];
for t in 1..=floors {
for k in 1..=eggs {
dp[t][k] = 1 + dp[t - 1][k - 1] + dp[t - 1][k];
if dp[t][k] >= floors && k == eggs {
return t;
}
}
}
floors // fallback (1 egg case)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic() {
assert_eq!(egg_drop_basic(1, 10), 10);
assert_eq!(egg_drop_basic(2, 10), 4);
assert_eq!(egg_drop_basic(2, 6), 3);
assert_eq!(egg_drop_basic(3, 14), 4);
}
#[test]
fn test_binary_search() {
assert_eq!(egg_drop_bs(1, 10), 10);
assert_eq!(egg_drop_bs(2, 10), 4);
assert_eq!(egg_drop_bs(2, 6), 3);
}
#[test]
fn test_optimal() {
assert_eq!(egg_drop_optimal(1, 10), 10);
assert_eq!(egg_drop_optimal(2, 10), 4);
assert_eq!(egg_drop_optimal(2, 6), 3);
assert_eq!(egg_drop_optimal(2, 100), 14);
}
}Key Differences
max_int / usize::MAX**: Both use a large sentinel as infinity for minimization; Rust's usize::MAX saturates on addition — use explicit comparison to avoid overflow.lo/hi bisection identically.dp[t][k] += dp[t-1][k-1] + dp[t-1][k]) is cleaner — only requires O(kn) instead of O(kn²) DP.OCaml Approach
let egg_drop eggs floors =
let dp = Array.make_matrix (eggs+1) (floors+1) 0 in
for i = 1 to eggs do
for j = 1 to floors do
if i = 1 then dp.(i).(j) <- j
else begin
dp.(i).(j) <- max_int;
for x = 1 to j do
let worst = 1 + max dp.(i-1).(x-1) dp.(i).(j-x) in
if worst < dp.(i).(j) then dp.(i).(j) <- worst
done
end
done
done;
dp.(eggs).(floors)
Identical structure. The binary search optimization has the same mathematical basis in both languages.
Full Source
#![allow(clippy::all)]
// 1074: Egg Drop — DP + Binary Search
// Approach 1: Basic DP O(k*n^2)
fn egg_drop_basic(eggs: usize, floors: usize) -> usize {
let mut dp = vec![vec![0usize; floors + 1]; eggs + 1];
for i in 1..=eggs {
for j in 1..=floors {
if i == 1 {
dp[i][j] = j;
} else {
dp[i][j] = usize::MAX;
for x in 1..=j {
let worst = 1 + dp[i - 1][x - 1].max(dp[i][j - x]);
dp[i][j] = dp[i][j].min(worst);
}
}
}
}
dp[eggs][floors]
}
// Approach 2: DP with binary search O(k*n*log(n))
fn egg_drop_bs(eggs: usize, floors: usize) -> usize {
let mut dp = vec![vec![0usize; floors + 1]; eggs + 1];
for i in 1..=eggs {
for j in 1..=floors {
if i == 1 {
dp[i][j] = j;
} else {
let (mut lo, mut hi) = (1, j);
while lo < hi {
let mid = (lo + hi) / 2;
if dp[i - 1][mid - 1] < dp[i][j - mid] {
lo = mid + 1;
} else {
hi = mid;
}
}
dp[i][j] = 1 + dp[i - 1][lo - 1].max(dp[i][j - lo]);
}
}
}
dp[eggs][floors]
}
// Approach 3: Optimal — how many floors can we check with t trials and k eggs?
fn egg_drop_optimal(eggs: usize, floors: usize) -> usize {
// dp[t][k] = max floors checkable with t trials and k eggs
let mut dp = vec![vec![0usize; eggs + 1]; floors + 1];
for t in 1..=floors {
for k in 1..=eggs {
dp[t][k] = 1 + dp[t - 1][k - 1] + dp[t - 1][k];
if dp[t][k] >= floors && k == eggs {
return t;
}
}
}
floors // fallback (1 egg case)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic() {
assert_eq!(egg_drop_basic(1, 10), 10);
assert_eq!(egg_drop_basic(2, 10), 4);
assert_eq!(egg_drop_basic(2, 6), 3);
assert_eq!(egg_drop_basic(3, 14), 4);
}
#[test]
fn test_binary_search() {
assert_eq!(egg_drop_bs(1, 10), 10);
assert_eq!(egg_drop_bs(2, 10), 4);
assert_eq!(egg_drop_bs(2, 6), 3);
}
#[test]
fn test_optimal() {
assert_eq!(egg_drop_optimal(1, 10), 10);
assert_eq!(egg_drop_optimal(2, 10), 4);
assert_eq!(egg_drop_optimal(2, 6), 3);
assert_eq!(egg_drop_optimal(2, 100), 14);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic() {
assert_eq!(egg_drop_basic(1, 10), 10);
assert_eq!(egg_drop_basic(2, 10), 4);
assert_eq!(egg_drop_basic(2, 6), 3);
assert_eq!(egg_drop_basic(3, 14), 4);
}
#[test]
fn test_binary_search() {
assert_eq!(egg_drop_bs(1, 10), 10);
assert_eq!(egg_drop_bs(2, 10), 4);
assert_eq!(egg_drop_bs(2, 6), 3);
}
#[test]
fn test_optimal() {
assert_eq!(egg_drop_optimal(1, 10), 10);
assert_eq!(egg_drop_optimal(2, 10), 4);
assert_eq!(egg_drop_optimal(2, 6), 3);
assert_eq!(egg_drop_optimal(2, 100), 14);
}
}
Deep Comparison
Egg Drop — Comparison
Core Insight
The egg drop problem asks for worst-case minimum trials. The key insight for the optimal solution: instead of asking "given k eggs and n floors, what's the minimum trials?", ask "given t trials and k eggs, what's the maximum floors we can check?" This gives the recurrence f(t,k) = 1 + f(t-1,k-1) + f(t-1,k).
OCaml Approach
ref cells for binary search pointersmax_int as initial sentinel for minimizationwhile loop with ref counter for optimal approachRust Approach
vec! DP tablelet (mut lo, mut hi)usize::MAX as sentinel.max() and .min()Comparison Table
| Aspect | OCaml | Rust |
|---|---|---|
| Sentinel | max_int | usize::MAX |
| Binary search | ref pointers + while | let (mut lo, mut hi) + while |
| Min/max | min/max functions | .min()/.max() methods |
| Optimal loop | while dp.(!t).(eggs) < floors | for t in 1..=floors + early return |
| Early exit | Increment ref counter | return t from inner loop |
Exercises
dp[t][k] formulation: minimum trials t such that dp[t][k] >= n, which avoids the O(n²) inner loop.f costs cost[f] — find the minimum expected cost.