Start learning free

LESSON 417 OF 574 · STACK

Car Fleet in Swift: 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 8
    Ctrl + Enter to check

    SWIFT · STACK

    Car Fleet in Swift, explained

    Problem overview

    Several cars drive on a one-lane road toward the same destination, target miles from the start. Car i starts at mile position[i] and drives at speed[i] miles per hour. A car can't pass the car ahead of it. When it catches up with a slower car, it slows down and the two drive on together, at the speed of the car in front. A group of cars driving together is a fleet, and a car alone is a fleet too. A car that reaches a fleet exactly at target belongs to it. The method carFleet returns how many fleets arrive at 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 arrive after 1 hour, so the second catches the first exactly at the target: one fleet. The car from mile 5 would take 7 hours, and the car from mile 3 only 3, so they meet and drive on together at speed 1: a second fleet. The car from mile 0 would need 12 hours and never catches anyone: a third. The answer is 3. In the third example, target = 100, position = [0, 2, 4] and speed = [4, 2, 1], each car catches up with the one ahead, and all three arrive together as one fleet.

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

    Approach: sort by position, then compare arrival times

    Only the order of the cars on the road matters. 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. Call that its own time.

    The car closest to the target can never be blocked, so it leads a fleet. For each car after that, look at the fleet just ahead of it. If the car's own time is no more than that fleet's time, the car catches the fleet, at the latest at the target, and joins it. If its own time is larger, it never catches up and starts a new fleet, which the cars behind it may still join.

    A fleet arrives when the car that leads it would arrive on its own, because the lead car is never slowed down. So a stack of the leading cars is enough: the top is the fleet just ahead of the next car. The answer is the stack's size.

    For the first example, the cars sorted from the target backward, with their own times, are:

    car 0 position 10 time 1.0
    car 1 position 8 time 1.0
    car 3 position 5 time 7.0
    car 4 position 3 time 3.0
    car 2 position 0 time 12.0
    

    Car 0 starts the first fleet. Car 1 takes 1.0 hour, which is not more than 1.0, so it joins. Car 3 takes 7.0, more than the top's 1.0, so it starts the second fleet. Car 4 takes 3.0, which is not more than 7.0, so it joins that fleet. Car 2 takes 12.0, more than 7.0, so it starts the third. The program prints 3.

    Step by step

    1. Sort the car numbers. order is position.indices.sorted { position[$0] > position[$1] }. Sorting the car numbers, not the positions, keeps each position tied to its speed: car order[k] starts at position[order[k]] and drives at speed[order[k]]. The closure puts the larger position first, so the car closest to the target comes first.
    2. Create the stack. fleets is an empty [Int]. For each fleet it holds the number of the car that leads it, and its last element is the top.
    3. Loop over order with the variable car.
    4. Decide if the car starts a new fleet. This happens when the stack is empty, or when the car would arrive later than the fleet on top, fleets.last!. A helper, arrivesLater, answers that.
    5. Push it with fleets.append(car). A car that doesn't start a fleet joins the one ahead, and there is nothing to do.
    6. Return fleets.count after the loop.
    7. Write the helper. arrivesLater takes the target, both arrays, the car and the lead car, and returns a Bool.
    8. Compare the times without dividing. The car arrives later when (target - position[car]) / speed[car] is greater than (target - position[lead]) / speed[lead]. Multiplying both sides by the two speeds, which are positive, gives (target - position[car]) * speed[lead] > (target - position[lead]) * speed[car], with no fractions. The comparison is strict: a car that arrives at the same moment catches up exactly at the target, and that still counts as one fleet.

    The solution in Swift

    class Solution {
        func carFleet(_ target: Int, _ position: [Int], _ speed: [Int]) -> Int {
            let order = position.indices.sorted { position[$0] > position[$1] }
            var fleets = [Int]()
            for car in order {
                if fleets.isEmpty || arrivesLater(target, position, speed, car, fleets.last!) {
                    fleets.append(car)
                }
            }
            return fleets.count
        }
    
        func arrivesLater(_ target: Int, _ position: [Int], _ speed: [Int], _ car: Int, _ lead: Int) -> Bool {
            return (target - position[car]) * speed[lead] > (target - position[lead]) * speed[car]
        }
    }
    

    The Swift details worth knowing:

    • position.indices is the range 0..<position.count. Calling sorted on it gives a new [Int] of car numbers, ordered by the closure. $0 and $1 are the two numbers being compared, and the closure says whether the first should come before the second.
    • fleets.last!. last is an optional, because an array can be empty. The ! unwraps it. It is safe because || stops at the first true part: when fleets.isEmpty is true, fleets.last! is never evaluated.
    • The underscores in the parameter lists mean the caller writes no labels, so the call reads arrivesLater(target, position, speed, car, fleets.last!).
    • Why multiply and not divide. Integer division in Swift truncates, so 7 / 2 and 6 / 2 are both 3, although the times differ. Converting to Double would work for most inputs, but comparing two quotients for equality is risky. The cross-multiplied form uses only integers, so equal times stay exactly equal.
    • Overflow. The products reach about a trillion (10^12), since a distance can be near a million and a speed too. Swift's Int is 64 bits, so they fit. In a 32-bit integer they would not: for Int32 values, 1_000_000 * 1_000_000 overflows, and Swift stops the program with a run-time trap instead of wrapping silently. Asking an Int32 for multipliedReportingOverflow(by:) on those values reports overflow: true. With Int the same product is 1000000000000.

    Complexity

    Time: O(n log n). Sorting the car numbers takes O(n log n). The loop then visits each car once and does a constant amount of work, so it is O(n).

    Space: O(n). The sorted array order holds n numbers, and the stack fleets can hold up to n as well, when no car ever catches another.

    Edge cases and common mistakes

    • A single car. target = 10, position = [3], speed = [3] gives 1: one car is one fleet.
    • Input not in road order. The positions are not sorted in the input. For target = 10, position = [0, 4, 2], speed = [2, 1, 3], the method sorts first and returns 1.
    • Using >= in the helper. A car that arrives at exactly the same time as the fleet ahead joins it. With a non-strict comparison it would start a fleet of its own, and the first example would give 4 instead of 3.
    • Cars that never meet. If every car is slower than the one ahead of it, none catches up. For target = 10, position = [0, 2, 4, 6, 8] and speed = [1, 1, 1, 1, 1], the answer is 5, because equal speeds keep a constant gap.
    • Faster cars behind. A fast car behind a slow one doesn't make a second fleet: target = 10, position = [0, 2, 4, 6, 8], speed = [5, 4, 3, 2, 1] gives 1.
    • Comparing positions instead of times. A car far behind with a high speed can still catch a nearby slow car. The arrival times decide, not the starting miles.
    • Sorting the positions alone. If you sort position without its speeds, the pairs break up. Sort the car numbers.

    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 = [0, 4, 2], speed = [2, 1, 3] gives 1.
    • target = 10, position = [6, 8], speed = [3, 2] gives 2.
    • target = 10, position = [0, 2, 4, 6, 8], speed = [1, 1, 1, 1, 1] gives 5.
    Keep going with Swift500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free