Start learning free

LESSON 419 OF 576 · STACK

Car Fleet in Rust: O(n log n) with Sorting and a Stack

Practice O(n log n) Read the full explanation

There are n cars on a one-lane road, all driving to the same destination, target miles from the start of the road. You are given two arrays of n integers, position and speed: car i starts at mile position[i] and drives at speed[i] miles per hour.

A car can never pass the car ahead of it. When it catches up with a slower car, it slows down and the two drive on together, bumper to bumper, at the speed of the car in front. A car fleet is a group of cars driving together like this; a car that drives alone is a fleet too. A car that catches up with a fleet exactly at target is part of that fleet.

Return the number of car fleets that arrive at target.

 

Example 1:

Input: target = 12, position = [10, 8, 0, 5, 3], speed = [2, 4, 1, 1, 3]
Output: 3
Explanation:
- The car from mile 10 would arrive after (12 - 10) / 2 = 1 hour, and so would the car from mile 8,
  after (12 - 8) / 4 = 1 hour. The second one catches up with the first exactly at the target: one fleet.
- The car from mile 5 (speed 1) would need 7 hours, the car from mile 3 (speed 3) only 3. They meet at
  mile 6 after 1 hour and drive on together at speed 1: a second fleet, arriving after 7 hours.
- The car from mile 0 (speed 1) would need 12 hours and never catches up with anyone: a third fleet.

Example 2:

Input: target = 10, position = [3], speed = [3]
Output: 1
Explanation: A single car is one fleet.

Example 3:

Input: target = 100, position = [0, 2, 4], speed = [4, 2, 1]
Output: 1
Explanation: The car from mile 0 catches up with the car from mile 2 at mile 4, after 1 hour,
and they drive on at speed 2. That fleet catches up with the car from mile 4 at mile 6, after 2 hours, and
all three drive on at speed 1, arriving together after 96 hours.

 

Constraints:

  • n is the length of both position and speed
  • 1 <= n <= 105
  • 1 <= target <= 106
  • 0 <= position[i] < target
  • All the values in position are different.
  • 1 <= speed[i] <= 106

Note:

Your algorithm should run in O(n log n) time and use O(n) extra space.



Only the order of the cars on the road matters, so visit them from the one closest to the target to the one farthest from it. If nothing slowed it down, car i would arrive after (target - position[i]) / speed[i] hours. A car that would arrive no later than the fleet just ahead of it catches up with that fleet, at the latest at the target, and joins it. A car that would arrive later never catches up: it starts a new fleet, which the cars behind it may join.

Keep the fleets found so far on a stack. A fleet arrives when the car that leads it would arrive on its own, since that car was never slowed down. The fleet just ahead of the next car is always the one on top of the stack, and it is also the one that arrives last: compare the car with it only. A car that would arrive later starts a new fleet and goes on top; otherwise it joins the top fleet. The answer is the size of the stack.

The times are fractions. Compare them exactly, without dividing: since the speeds are positive, (target - p1) / s1 > (target - p2) / s2 is the same as (target - p1) * s2 > (target - p2) * s1. These products can reach 1012, more than a 32-bit integer holds: where integers are 32 bits, compute them with 64-bit integers. A Python integer, or a JavaScript or TypeScript number (exact for whole numbers below 253), holds them as it is.

Sorting the cars by position takes O(n log n) time. The pass over the sorted cars does a constant amount of work for each car, so sorting is the larger part. The sorted order and the stack hold at most n cars each: O(n) extra space.
STEP 1 OF 9
    Ctrl + Enter to check

    RUST · STACK

    Car Fleet in Rust, explained

    Problem overview

    The function car_fleet receives a target (the distance in miles to the destination) and two vectors of the same length: position and speed. Car i starts at mile position[i] and drives at speed[i] miles per hour toward target. The road has one lane, so a car can never pass the one ahead. When a car catches up with a slower car, it slows to that car's speed and they drive on together. Such a group is a car fleet, and a car that drives alone is a fleet of one. A car that catches a fleet exactly at target still belongs to it. The function returns how many fleets reach target.

    In the first example, target = 12, position = [10, 8, 0, 5, 3] and speed = [2, 4, 1, 1, 3]. The cars from miles 10 and 8 would both need 1 hour, so the car behind catches the front one exactly at the target: one fleet. The car from mile 5 would need 7 hours, and the car from mile 3 catches it at mile 6 and slows down to its speed: a second fleet. The car from mile 0 needs 12 hours and catches nobody: a third. The answer is 3.

    The lesson asks for O(n log n) time and O(n) extra space.

    Approach: sort by position and compare arrival times

    Only the order of the cars on the road matters, so visit them from the one closest to the target to the one farthest away. If nothing slowed it down, car i would arrive after (target - position[i]) / speed[i] hours. The first car in this order is never blocked. Every later car is behind the cars already seen, and it meets one of two outcomes:

    • It would arrive no later than the fleet just ahead. It catches up with that fleet, at the latest at the target, and joins it.
    • It would arrive later. It never catches up, so it starts a new fleet, which cars further back may still join.

    A fleet arrives when its lead car would arrive on its own, since that car was never slowed. So a stack of lead cars is enough. The fleet just ahead of the next car is always the one on top, and it is also the one that arrives last, so the car is compared with the top only. The answer is the size of the stack.

    For the first example, the cars sorted by position, closest to the target first, are the car numbers [0, 1, 3, 4, 2], and their arrival times are [1.0, 1.0, 7.0, 3.0, 12.0]. Car 0 starts a fleet. Car 1 arrives at the same time, so it joins. Car 3 needs 7, more than 1, so it starts a fleet. Car 4 needs 3, no more than 7, so it joins. Car 2 needs 12, so it starts a third fleet.

    Comparing times without dividing

    The times are fractions, and equal times must count as equal, so the code avoids floating point. Because speeds are positive, (target - p1) / s1 > (target - p2) / s2 holds exactly when (target - p1) * s2 > (target - p2) * s1. Multiplying both sides by the two speeds removes the fractions and keeps the comparison the same.

    Step by step

    1. Make the car numbers: a mutable Vec<usize> named order from (0..position.len()).collect(). Sorting car numbers, not positions, keeps each position tied to its speed.
    2. Sort order by position, largest first, with sort_by and the closure |&a, &b| position[b].cmp(&position[a]). Comparing b with a instead of a with b reverses the order.
    3. Create the stack: an empty Vec<usize> named fleets. It holds the number of the lead car of each fleet found so far, and its last element is the top.
    4. Loop over &order with the pattern &car, which borrows the vector and copies each number out.
    5. Decide whether this car starts a fleet. It does when the stack is empty, or when it would arrive later than the top fleet. The helper arrives_later answers the second question, and || reads the top only when the stack isn't empty.
    6. Push the car with fleets.push(car). If the car doesn't start a fleet, it joins the top one and nothing changes.
    7. Return the stack's size with fleets.len() as i32, converting the usize to the return type.
    8. Write the helper arrives_later. It takes target, two borrowed slices &[i32] for the positions and speeds, the number of the car and the number of the lead car, and returns a bool. It must be strict: a car that arrives at the same moment joins the fleet.
    9. Compare with 64-bit products. Each factor is converted with as i64 before multiplying.

    The solution in Rust

    impl Solution {
        fn car_fleet(target: i32, position: Vec<i32>, speed: Vec<i32>) -> i32 {
            let mut order: Vec<usize> = (0..position.len()).collect();
            order.sort_by(|&a, &b| position[b].cmp(&position[a]));
            let mut fleets: Vec<usize> = Vec::new();
            for &car in &order {
                if fleets.is_empty() || Self::arrives_later(target, &position, &speed, car, *fleets.last().unwrap()) {
                    fleets.push(car);
                }
            }
            fleets.len() as i32
        }
    
        fn arrives_later(target: i32, position: &[i32], speed: &[i32], car: usize, lead: usize) -> bool {
            (target - position[car]) as i64 * speed[lead] as i64 > (target - position[lead]) as i64 * speed[car] as i64
        }
    }
    

    The Rust details worth a closer look:

    • collect() needs a target type. The annotation Vec<usize> tells it what to build. The range 0..position.len() yields usize values, the type Rust uses for indexes.
    • The sort closure. sort_by passes references to two elements, and the pattern |&a, &b| copies the numbers out. The closure uses them to look up positions, and cmp returns an Ordering. The positions are all different, so the order is fully determined.
    • last().unwrap(). last() returns an Option<&usize>, unwrap() takes out the reference and * copies the number. || stops at the first true part, so unwrap never runs on an empty stack.
    • Slices as parameters. The helper takes &[i32], so the call passes &position, a &Vec<i32>, which Rust converts to a slice automatically. The helper borrows the data and does not take it over.
    • as binds tighter than *. In (target - position[car]) as i64 * speed[lead] as i64, each operand is converted first and then the two 64-bit numbers are multiplied.
    • Why 64 bits. The products can reach about a trillion (10^12), far past the i32 maximum of about 2.1 billion. For example 1,000,000 times 999,999 is 999999000000 as an i64, while the same product computed with wrapping i32 arithmetic is -728379968. An ordinary i32 multiplication of these two numbers gives the same -728379968 in a build with -O, and stops a debug build with the panic attempt to multiply with overflow.
    • The difference target - position[car] stays in i32 because positions are below target and both are at most 1,000,000, so it can't overflow.

    Complexity

    Time: O(n log n). Sorting the car numbers is O(n log n). The loop does constant work per car, so sorting is the larger part.

    Space: O(n). The vector order and the stack fleets hold at most n numbers each.

    Edge cases and common mistakes

    • A single car. target = 10 with one car at mile 3 returns 1: the stack is empty at the first car, so it starts a fleet.
    • A back car that arrives later. With target = 10, positions [6, 8] and speeds [3, 2], the front car needs 1 hour and the back car needs 4/3 of an hour, so it never catches up and the answer is 2. Equal arrival times, as in the first example, count as one fleet, which is why the comparison is strictly greater.
    • Same speed, never meeting. target = 10, positions [0, 5], speeds [1, 1] returns 2: the back car never gains on the front one.
    • A chain of catch-ups. With target = 100, positions [0, 2, 4] and speeds [4, 2, 1], each car would arrive sooner than the one ahead, so each joins it and the answer is 1.
    • Unsorted input. Positions come in any order. In the first example the closest-to-target order is [0, 1, 3, 4, 2], not [0, 1, 2, 3, 4].
    • Floating-point division. Dividing and comparing f64 values can make two equal times look different. The integer products stay exact.
    • Using 32-bit products. They overflow on large inputs, as in the example above.
    • Comparing with the wrong fleet. Only the top of the stack matters: it is the fleet directly ahead, and the one that arrives last.

    Testing

    • target = 12, position = [10, 8, 0, 5, 3], speed = [2, 4, 1, 1, 3] gives 3.
    • target = 10, position = [3], speed = [3] gives 1.
    • target = 100, position = [0, 2, 4], speed = [4, 2, 1] gives 1.
    • target = 10, position = [6, 8], speed = [3, 2] gives 2.
    • target = 10, position = [0, 5], speed = [1, 1] gives 2.
    • target = 10, position = [0, 4, 2], speed = [2, 1, 3] gives 1.
    • target = 1000000, position = [0, 1], speed = [1000000, 999999] gives 1.
    Keep going with Rust500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free