RustArena

Step 2 of 7

Linear and binary search

Linear search looks at the items one by one: v.contains(&x), v.iter().position(|&y| y == x). It's O(n) and works on any data.

Binary search needs sorted data and is O(log n): look at the middle item; if it's too small, the answer can only be in the right half, otherwise in the left half. Each step halves what's left, so a million items take at most 20 steps.

Slices have it built in:

fn main() {
    let scores = [12, 25, 25, 25, 40, 58, 71];
    println!("{:?}", scores.binary_search(&40));
    println!("{:?}", scores.binary_search(&30));

    let first_25 = scores.partition_point(|&s| s < 25);
    let after_25 = scores.partition_point(|&s| s <= 25);
    println!("25 is at {first_25}..{after_25}, {} times", after_25 - first_25);

    let passed = scores.len() - scores.partition_point(|&s| s < 50);
    println!("{passed} scores are 50 or more");

    let mut sorted = vec![10, 20, 30];
    let pos = sorted.binary_search(&25).unwrap_or_else(|insert_at| insert_at);
    sorted.insert(pos, 25);
    println!("{sorted:?}");
}
Ok(4)
Err(4)
25 is at 1..4, 3 times
2 scores are 50 or more
[10, 20, 25, 30]

With duplicates, binary_search may return any of the matching positions; use partition_point when you need the first or last one.

Writing it yourself

Interviewers love asking for binary search by hand because it's easy to get subtly wrong. The safest shape is a half-open range lo..hi: the answer, if it exists, is at an index with lo <= index < hi. Start with lo = 0, hi = len, and loop while lo < hi:

Common mistakes

Your task

Write fn find(sorted: &[i32], target: i32) -> Option<usize> by hand with a half-open range (no binary_search or partition_point). The values are sorted and all different; return the index of target, or None. The tests look up 200,000 values in a slice of 200,000, so a linear scan is too slow.

Challenge 2

Reaction times (in milliseconds) from a game are kept sorted. The analytics page asks many questions like "how many players reacted between 180 and 250 ms (inclusive)?".

Write fn count_in_range(sorted: &[u32], low: u32, high: u32) -> usize with two calls to partition_point: one finds where values >= low start, the other where values > high start. Return 0 when low > high. The tests ask 100,000 questions about 200,000 values.

Challenge 3

Binary search on the answer. A warehouse ships packages along a conveyor in a fixed order. Each day the truck is loaded with packages in order for as long as the load stays within its capacity (a load exactly equal to the capacity is fine); packages are never split. Write fn min_capacity(packages: &[u32], days: u32) -> u32: the smallest truck capacity that ships everything within days days (days is at least 1). Return 0 for no packages.

The trick: if capacity c is enough, every bigger capacity is too. So "is c enough?" is false, false, ..., true, true over the capacities, and you can binary search for the first true between max(package) (the smallest that can carry everything) and sum(packages) (one day). Write a helper that counts the days a capacity needs. The tests have 100,000 packages, so trying capacities one by one is too slow.

Previous: Big-O: counting the work Next: Sorting and custom orderings