Car Fleet
MedPrompt
There are n cars at given miles away from the starting mile 0, traveling to reach the mile target.
You are given two integer array position and speed, both of length n, where position[i] is the starting mile of the ith car and speed[i] is the speed of the ith car in miles per hour.
A car cannot pass another car, but it can catch up and then travel next to it at the speed of the slower car.
A car fleet is a car or cars driving next to each other. The speed of the car fleet is the minimum speed of any car in the fleet.
If a car catches up to a car fleet at the mile target, it will still be considered as part of the car fleet.
Return the number of car fleets that will arrive at the destination.
Example 1:
Input: target = 12, position = [10,8,0,5,3], speed = [2,4,1,1,3]
Output: 3
Explanation:
- The cars starting at 10 (speed 2) and 8 (speed 4) become a fleet, meeting each other at 12. The fleet forms at
target. - The car starting at 0 (speed 1) does not catch up to any other car, so it is a fleet by itself.
- The cars starting at 5 (speed 1) and 3 (speed 3) become a fleet, meeting each other at 6. The fleet moves at speed 1 until it reaches
target.
Example 2:
Input: target = 10, position = [3], speed = [3]
Output: 1
Explanation:
There is only one car, hence there is only one fleet.Example 3:
Input: target = 100, position = [0,2,4], speed = [4,2,1]
Output: 1
Explanation:
- The cars starting at 0 (speed 4) and 2 (speed 2) become a fleet, meeting each other at 4. The car starting at 4 (speed 1) travels to 5.
- Then, the fleet at 4 (speed 2) and the car at position 5 (speed 1) become one fleet, meeting each other at 6. The fleet moves at speed 1 until it reaches
target.
Constraints:
n == position.length == speed.length1 <= n <= 1050 < target <= 1060 <= position[i] < target- All the values of
positionare unique. 0 < speed[i] <= 106
Approaches
2 approaches with complexity analysis and trade-offs.
This approach first sorts the cars based on their starting positions. Then, it simulates the process of merging fleets. It repeatedly scans through the cars from back to front (those further from the target to those closer), merging any car that would catch up to the one immediately in front of it. This simulation continues until no more merges can be made in a full pass, at which point the number of remaining cars (or fleets) is the answer.
Algorithm
- Create a list of custom
Carobjects, where each object stores a car's position and its calculated time to reach the target. The time is(target - position) / speed. - Sort this list of cars in ascending order of their positions.
- Use a
whileloop that continues as long as merges occur in a pass. - Inside the loop, iterate through the cars from second-to-last to the first (
ifromn-2down to0). - If the arrival time of car
iis less than or equal to the arrival time of cari+1(the one in front), it means carimerges into the fleet ofi+1. - To handle the merge, remove car
ifrom the list. Set a flag indicating a merge occurred and break the inner loop to restart the scan from the beginning of the modified list. - If the
whileloop completes a full pass over the cars without any merges, the fleets are stable. The loop terminates. - The final number of fleets is the size of the remaining list of cars.
Walkthrough
The core idea is to directly simulate the physical process. After sorting the cars by position, we know their relative order. A car can only merge with the one directly ahead of it. We can use a dynamic list of cars and iterate through it. If we find a car i that will catch up to car i+1 (meaning time[i] <= time[i+1]), we merge them by removing car i. Since this merge could cause the car previously at i-1 to now be able to merge with the fleet at i+1, we must restart our scan. This process is repeated until a full scan of all cars results in no merges, indicating that the final fleet configuration has been reached.
class Car { int position; double time; Car(int p, double t) { this.position = p; this.time = t; }} public int carFleet(int target, int[] position, int[] speed) { if (position.length <= 1) return position.length; List<Car> cars = new ArrayList<>(); for (int i = 0; i < position.length; i++) { cars.add(new Car(position[i], (double)(target - position[i]) / speed[i])); } // Sort by position cars.sort(Comparator.comparingInt(a -> a.position)); while (true) { boolean mergedInPass = false; for (int i = cars.size() - 2; i >= 0; i--) { // Car i is behind car i+1 if (cars.get(i).time <= cars.get(i+1).time) { // Car i merges into the fleet of car i+1. // The new fleet's arrival time is that of car i+1. // We can just remove car i. cars.remove(i); mergedInPass = true; // Restart scan since the list has changed break; } } if (!mergedInPass) { break; // No merges in a full pass, we are done. } } return cars.size();}Complexity
Time
O(N^2) in the worst case. Sorting takes an initial O(N log N). The `while` loop can run up to N-1 times, and each pass involves an O(N) scan. This leads to a quadratic runtime.
Space
O(N) to store the list of `Car` objects for the simulation.
Trade-offs
Pros
The logic is a direct simulation of the problem statement, which can be easier to conceptualize initially.
Cons
The time complexity is O(N^2) in the worst case, which is too slow for the given constraints and will likely result in a 'Time Limit Exceeded' error.
The implementation is more complex than the optimal solution, involving repeated modifications and iterations over a list.
This optimal approach hinges on a key insight: a car's behavior is only constrained by the cars ahead of it. By sorting the cars by position and then processing them from front-to-back (i.e., from closest to the target to furthest), we can make a simple greedy decision for each car. In a single pass, we can determine if a car forms a new fleet or merges with an existing one, leading to a highly efficient solution.
Algorithm
- Create an array of
Carobjects, each storing apositionandspeed. - Sort this array of cars based on their starting
positionin ascending order. - Initialize a
fleetscounter to 0 and aleadTimevariable to track the arrival time of the current leading fleet (initialized to -1.0 or 0.0). - Iterate through the sorted cars in reverse order, from the car closest to the target (
n-1) to the one furthest away (0). - For each car, calculate its individual arrival time:
currentTime = (target - position) / speed. - Compare
currentTimewithleadTime:- If
currentTime > leadTime, it means this car is slower than the fleet ahead of it and cannot catch up. It must form a new fleet. Incrementfleetsand updateleadTimetocurrentTime, as this car is now the new bottleneck for any cars behind it. - If
currentTime <= leadTime, this car is fast enough to catch the fleet ahead. It merges into that fleet. No changes are needed forfleetsorleadTime.
- If
- After the loop completes, return the final
fleetscount.
Walkthrough
The logic is that if car A is behind car B, they form a fleet only if car A can catch up to car B before or at the target. This is equivalent to saying that the time it would take car A to reach the target (if unobstructed) is less than or equal to the time it would take car B.
By sorting the cars by position, we can iterate from the one closest to the target backwards. The car closest to the target is always the leader of a fleet. We record its arrival time. Then, for the next car behind it, we check if its arrival time is less than or equal to the leader's time. If it is, it merges. If it's greater, it's too slow and forms a new fleet, becoming the new 'leader' for the cars behind it. This process continues, maintaining the arrival time of the slowest fleet currently at the front. This is a form of monotonic stack logic, where the arrival times of the fleet leaders we identify will be strictly increasing.
class Car { int position; int speed; Car(int p, int s) { this.position = p; this.speed = s; }} public int carFleet(int target, int[] position, int[] speed) { int n = position.length; if (n <= 1) { return n; } Car[] cars = new Car[n]; for (int i = 0; i < n; i++) { cars[i] = new Car(position[i], speed[i]); } // Sort cars by position in ascending order Arrays.sort(cars, (a, b) -> Integer.compare(a.position, b.position)); int fleets = 0; double leadTime = -1.0; // Arrival time of the current fleet leader // Iterate from the car closest to the target backwards for (int i = n - 1; i >= 0; i--) { double currentTime = (double)(target - cars[i].position) / cars[i].speed; if (currentTime > leadTime) { // This car is slower than the fleet in front of it, // so it becomes the leader of a new fleet. fleets++; leadTime = currentTime; } // Otherwise, this car will catch up and merge with the fleet ahead. // The number of fleets and the leadTime do not change. } return fleets;}Complexity
Time
O(N log N). The dominant operation is sorting the cars by position. The subsequent greedy scan is a single pass, taking O(N) time.
Space
O(N) to store the `Car` objects or pairs needed for sorting. If sorting can be done in-place on the input arrays (by pairing them logically), space could be O(1), but this is often not practical.
Trade-offs
Pros
Optimal time complexity of O(N log N), which is very efficient and passes all constraints.
The logic is clean and requires only a single pass over the data after sorting.
Uses a constant amount of extra space besides the storage required for sorting.
Cons
The main performance bottleneck is the O(N log N) sorting step.
Requires the use of floating-point arithmetic for time calculations, which can have precision issues in some problems, though it's safe here.
Solutions
Solution
class Solution { public int carFleet ( int target , int [] position , int [] speed ) { int n = position . length ; Integer [] idx = new Integer [ n ]; for ( int i = 0 ; i < n ; ++ i ) { idx [ i ] = i ; } Arrays . sort ( idx , ( i , j ) -> position [ j ] - position [ i ]); int ans = 0 ; double pre = 0 ; for ( int i : idx ) { double t = 1.0 * ( target - position [ i ]) / speed [ i ]; if ( t > pre ) { ++ ans ; pre = t ; } } return ans ; } }class Solution { public: int carFleet ( int target , vector < int >& position , vector < int >& speed ) { int n = position . size (); vector < int > idx ( n ); iota ( idx . begin (), idx . end (), 0 ); sort ( idx . begin (), idx . end (), [ & ]( int i , int j ) { return position [ i ] > position [ j ]; }); int ans = 0 ; double pre = 0 ; for ( int i : idx ) { double t = 1.0 * ( target - position [ i ]) / speed [ i ]; if ( t > pre ) { ++ ans ; pre = t ; } } return ans ; } };class Solution : def carFleet ( self , target : int , position : List [ int ], speed : List [ int ]) -> int : idx = sorted ( range ( len ( position )), key = lambda i : position [ i ]) ans = pre = 0 for i in idx [:: - 1 ]: t = ( target - position [ i ]) / speed [ i ] if t > pre : ans += 1 pre = t return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.