Start learning free

LESSON 412 OF 569 · STACK

Car Fleet in Python: 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 lists 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

    PYTHON · STACK

    Car Fleet in Python, explained

    Problem overview

    The method carFleet receives a target (the mile where the road ends) and two lists, position and speed. Car i starts at mile position[i] and drives at speed[i] miles per hour. Cars can't pass each other. A car that catches up with a slower one has to slow down and drive on at the speed of the car in front, bumper to bumper. Cars driving together form a fleet, and a lone car is a fleet too. A car that catches up exactly at the target is part of that fleet. The method returns how many fleets arrive at the target.

    For target = 12, position = [10, 8, 0, 5, 3] and speed = [2, 4, 1, 1, 3] the answer is 3:

    • The cars at miles 10 and 8 would both arrive after 1 hour, so the one behind catches up at the target exactly. That is one fleet.
    • The car at mile 5 (speed 1) would need 7 hours. The car at mile 3 (speed 3) would need 3, so it catches up and the two continue at speed 1. That is a second fleet.
    • The car at mile 0 (speed 1) would need 12 hours and never catches anyone. That is a third fleet.

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

    Approach: go from the front car backwards and compare arrival times

    Only the order of the cars on the road matters, so look at them from the one closest to the target to the one farthest away. Driving alone, car i would arrive after (target - position[i]) / speed[i] hours. The car at the very front is never blocked. Every other car can only be held up by the cars ahead of it, and those were already handled.

    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, so it starts a new fleet, which the cars behind it may join. A fleet's arrival time is the time of the car that leads it, because that car was never slowed down.

    That suggests a stack of fleets, each represented by its lead car. The fleet just ahead of the next car is always the one on top, and it is also the one that arrives last so far, so one comparison with the top is enough. A car that arrives later goes on the stack; otherwise it joins the top fleet and nothing changes. The answer is the size of the stack.

    The same example, visited in order of position (cars 0, 1, 3, 4, 2):

    carpositionspeedalone, arrives afterresultfleet leaders afterwards
    01021.0 hnew fleet[0]
    1841.0 hjoins[0]
    3517.0 hnew fleet[0, 3]
    4333.0 hjoins[0, 3]
    20112.0 hnew fleet[0, 3, 2]

    Car 1 ties with car 0 at 1.0 hour, which counts as joining. The stack ends with three leaders, so the answer is 3.

    Step by step

    1. Make the car numbers: order = list(range(len(position))). Sorting the numbers rather than the positions keeps each car's position and speed together: car order[k] starts at position[order[k]] and drives at speed[order[k]].
    2. Sort order by position, closest to the target first, with order.sort(key=lambda car: position[car], reverse=True).
    3. Create the empty list fleets, the stack of lead cars.
    4. Loop over order with the variable car.
    5. Test for a new fleet: the stack is empty, or the car would arrive later than the top fleet, which the helper arrivesLater decides.
    6. Push the car with fleets.append(car) when it starts a new fleet. Otherwise do nothing: it joined.
    7. Return len(fleets).
    8. Write the helper arrivesLater below carFleet, taking self, target, position, speed, car, lead.
    9. Compare without dividing. car would arrive after (target - position[car]) / speed[car] hours and lead after (target - position[lead]) / speed[lead]. Multiply both sides by the two positive speeds and compare (target - position[car]) * speed[lead] with (target - position[lead]) * speed[car]. The comparison must be strict: a car arriving at the same moment still belongs to the fleet.

    The solution in Python

    class Solution:
        def carFleet(self, target: int, position: List[int], speed: List[int]) -> int:
            order = list(range(len(position)))
            order.sort(key=lambda car: position[car], reverse=True)
            fleets = []
            for car in order:
                if not fleets or self.arrivesLater(target, position, speed, car, fleets[-1]):
                    fleets.append(car)
            return len(fleets)
        def arrivesLater(self, target, position, speed, car, lead):
            return (target - position[car]) * speed[lead] > (target - position[lead]) * speed[car]
    

    Python details worth knowing:

    • sort with a key. order.sort(...) sorts the list in place and returns None, which is why the code doesn't assign its result. The key function receives each car number and returns the value to sort by: lambda car: position[car] is a one-line function doing exactly that. reverse=True puts the largest positions first.
    • Short-circuit or. In not fleets or ..., Python stops at the first true part. When the stack is empty, fleets[-1] is never evaluated, which would raise an IndexError.
    • fleets[-1] is the last element of the list, the top of the stack.
    • Exact products. Python integers have no fixed size, so the products, up to about 10^12 here, can't overflow. In a language with 32-bit integers they would need a wider type.
    • Methods calling methods. The helper is called as self.arrivesLater(...), and its parameters get the values of the arguments in order after self.

    Complexity

    Time: O(n log n). Sorting the car numbers is O(n log n). The loop does a constant amount of work per car (one comparison and at most one push), so it is O(n), and the sort is the larger part.

    Space: O(n). The list order holds n numbers, and the stack holds at most n.

    Edge cases and common mistakes

    • A single car. target = 10 with one car returns 1.
    • A chain of catch-ups. For target = 100, position = [0, 2, 4] and speed = [4, 2, 1] every car is faster than the one ahead, but each ends up behind the slowest. The result is 1.
    • Ties. Equal arrival times mean one fleet, as the first example shows with cars 0 and 1. Using >= in the helper would count a tied car as a new fleet: the first example would return 4 instead of 3.
    • Input in any order. The positions are not sorted in the input. Visiting the cars in input order breaks the stack logic. For target = 10, position = [0, 4, 2] and speed = [2, 1, 3] the answer is 1, but skipping the sort gives 2.
    • Sorting the positions alone. Sorting only position loses which speed belongs to which car. Sorting car numbers with a key keeps them together.
    • A fast car far behind. For target = 10, position = [0, 5] and speed = [5, 1], the car at mile 0 would arrive after 2 hours, before the car at mile 5, which needs 5. It catches up and joins, so the answer is 1.
    • A car that stays behind. For target = 10, position = [6, 8] and speed = [3, 2], the rear car needs 4/3 of an hour and the front car 1 hour. The rear car arrives later and never catches up, so the answer is 2.
    • Comparing against the wrong fleet. Only the top of the stack matters. It is the fleet that arrives last, and a car that arrives no later than it also arrives no later than any fleet below.

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