1776. Car Fleet II
Problem Description
You have n cars driving in the same direction on a single-lane road. Each car has a starting position and speed given in the array cars, where cars[i] = [position_i, speed_i]:
position_iis the distance (in meters) of thei-th car from the start of the roadspeed_iis the speed (in meters per second) of thei-th car- Cars are ordered by position, so
position_i < position_{i+1}
When a faster car catches up to a slower car ahead of it, they collide and form a fleet. After collision:
- The cars move together as a single unit
- The fleet moves at the speed of the slower car
Your task is to determine when each car will collide with the car directly in front of it. Return an array answer where answer[i] represents:
- The time (in seconds) when car
icollides with cari+1 -1if carinever collides with the next car
The solution uses a monotonic stack approach, processing cars from right to left. For each car, it checks if and when it will catch up to cars ahead of it, considering that some cars ahead might have already collided with others. The stack maintains cars that could potentially be collision targets, removing those that would be "bypassed" due to earlier collisions.
The collision time between car i and car j (where j is ahead) is calculated as: t = (position_j - position_i) / (speed_i - speed_j), which only applies when car i is faster than car j.
Intuition
The key insight is to process the cars from right to left (back to front). Why? Because a car can only collide with cars ahead of it, never behind. When we process from the back, we already know the collision information for all cars ahead, which helps us make decisions for the current car.
Consider what happens when car i is faster than car j ahead of it. Car i will eventually catch up to car j at time t = (position_j - position_i) / (speed_i - speed_j). But there's a catch - car j might collide with another car before car i reaches it. If car j collides at time ans[j] and this happens before car i would reach j, then car i won't actually collide with j directly.
This leads us to think about which cars are "reachable" for collision. If car i would reach car j after j has already collided with someone else, we need to look further ahead. The car that j collided with might be our actual collision target, or perhaps an even further car.
A monotonic stack naturally handles this scenario. As we process each car from right to left:
- We maintain a stack of potential collision targets
- For the current car, we check the top of the stack (the nearest car ahead)
- If the current car is slower or same speed, it will never catch up - we add it to the stack for future cars to consider
- If the current car is faster, we calculate when it would collide
- If this collision happens after the target car has already collided elsewhere, we pop that target from the stack and check the next one
- We continue popping until we find a valid collision or run out of targets
The stack effectively maintains a "chain" of cars where each subsequent car either doesn't collide or collides before being reached by cars behind it. This ensures we always find the correct first collision for each car.
Learn more about Stack, Math, Monotonic Stack and Heap (Priority Queue) patterns.
Solution Approach
The solution uses a monotonic stack to efficiently track potential collision targets while processing cars from right to left.
Data Structure:
stk: A stack storing indices of cars that could be collision targetsans: Result array initialized with-1for all cars
Algorithm Steps:
-
Iterate from right to left (
ifromn-1to0):- Start from the rightmost car since it has no car ahead to collide with
- Each car only needs to consider cars ahead of it
-
For each car
i, check potential collisions:while stk: j = stk[-1] # Get the nearest car ahead -
Determine if collision is possible:
- Check if car
iis faster:cars[i][1] > cars[j][1] - If not faster, car
iwill never catch up, so pop carjfrom stack and check next
- Check if car
-
Calculate collision time if car
iis faster:t = (cars[j][0] - cars[i][0]) / (cars[i][1] - cars[j][1])This formula comes from solving:
position_i + speed_i * t = position_j + speed_j * t -
Validate the collision:
- Check if
ans[j] == -1(carjdoesn't collide with anyone) - OR check if
t <= ans[j](carireachesjbeforejcollides with another car) - If valid, set
ans[i] = tand break - If invalid, pop car
jfrom stack (it's unreachable) and continue checking
- Check if
-
Add current car to stack:
stk.append(i)After processing, add car
ito the stack as a potential target for cars behind it
Why this works:
- The stack maintains cars in increasing order of "reachability"
- When we pop a car from the stack, it means it's "blocked" by its own collision and won't be the direct collision target
- The first valid collision we find is guaranteed to be the actual collision for car
i - Cars that never collide remain
-1in the answer array
Time Complexity: O(n) - each car is pushed and popped from the stack at most once
Space Complexity: O(n) - for the stack and answer array
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with 4 cars:
cars = [[0, 2], [4, 1], [8, 3], [10, 1]]
Visual representation:
Position: 0 4 8 10 Speed: 2 1 3 1 Car: 0 1 2 3
Processing from right to left:
Step 1: Process car 3 (position=10, speed=1)
- Stack is empty, no car ahead
ans[3] = -1- Push 3 to stack:
stk = [3]
Step 2: Process car 2 (position=8, speed=3)
- Stack has car 3 (position=10, speed=1)
- Car 2 is faster (3 > 1), can catch up
- Collision time:
t = (10-8)/(3-1) = 1 second - Car 3 doesn't collide with anyone (
ans[3] = -1), so this collision is valid ans[2] = 1.0- Push 2 to stack:
stk = [3, 2]
Step 3: Process car 1 (position=4, speed=1)
- Stack top is car 2 (position=8, speed=3)
- Car 1 is slower (1 < 3), will never catch up
- Pop car 2:
stk = [3] - Check car 3 (position=10, speed=1)
- Same speed (1 = 1), will never catch up
- Pop car 3:
stk = [] - No more cars to check
ans[1] = -1- Push 1 to stack:
stk = [1]
Step 4: Process car 0 (position=0, speed=2)
- Stack has car 1 (position=4, speed=1)
- Car 0 is faster (2 > 1), can catch up
- Collision time:
t = (4-0)/(2-1) = 4 seconds - Car 1 doesn't collide with anyone (
ans[1] = -1), so this collision is valid ans[0] = 4.0- Push 0 to stack:
stk = [1, 0]
Final answer: [4.0, -1, 1.0, -1]
Interpretation:
- Car 0 collides with car 1 after 4 seconds
- Car 1 never collides (moves at same speed as car 3, slower than car 2)
- Car 2 collides with car 3 after 1 second
- Car 3 has no car ahead to collide with
The key insight demonstrated here is how car 1, despite being between cars 0 and 2, never collides with anyone because it's too slow to catch car 3 and car 2 (which is faster) collides with car 3 first.
Solution Implementation
1class Solution:
2 def getCollisionTimes(self, cars: List[List[int]]) -> List[float]:
3 # Stack to maintain cars that could potentially collide with current car
4 stack = []
5 n = len(cars)
6 # Initialize result array with -1 (no collision)
7 result = [-1] * n
8
9 # Process cars from right to left (last car to first car)
10 for i in range(n - 1, -1, -1):
11 # Check potential collisions with cars ahead (in the stack)
12 while stack:
13 j = stack[-1]
14
15 # Current car is faster than car j ahead
16 if cars[i][1] > cars[j][1]:
17 # Calculate collision time between car i and car j
18 collision_time = (cars[j][0] - cars[i][0]) / (cars[i][1] - cars[j][1])
19
20 # Valid collision if car j doesn't collide before or collides after this time
21 if result[j] == -1 or collision_time <= result[j]:
22 result[i] = collision_time
23 break
24
25 # Remove car j from stack as it won't collide with cars before i
26 stack.pop()
27
28 # Add current car to stack for processing previous cars
29 stack.append(i)
30
31 return result
321class Solution {
2 public double[] getCollisionTimes(int[][] cars) {
3 int n = cars.length;
4 double[] answer = new double[n];
5 // Initialize all collision times to -1 (no collision)
6 Arrays.fill(answer, -1.0);
7
8 // Stack to maintain indices of cars that could potentially collide
9 Deque<Integer> stack = new ArrayDeque<>();
10
11 // Process cars from right to left
12 for (int i = n - 1; i >= 0; i--) {
13 // Check potential collisions with cars ahead
14 while (!stack.isEmpty()) {
15 int j = stack.peek();
16
17 // Check if current car is faster than the car ahead
18 if (cars[i][1] > cars[j][1]) {
19 // Calculate collision time between car i and car j
20 double collisionTime = (double)(cars[j][0] - cars[i][0]) / (cars[i][1] - cars[j][1]);
21
22 // Check if this collision happens before car j collides with another car
23 // If car j doesn't collide with anyone (answer[j] < 0) or
24 // if collision happens before car j's collision time
25 if (answer[j] < 0 || collisionTime <= answer[j]) {
26 answer[i] = collisionTime;
27 break;
28 }
29 }
30 // Remove car j from stack as it won't affect cars to the left of i
31 stack.pop();
32 }
33 // Add current car to the stack for future consideration
34 stack.push(i);
35 }
36
37 return answer;
38 }
39}
401class Solution {
2public:
3 vector<double> getCollisionTimes(vector<vector<int>>& cars) {
4 int n = cars.size();
5 vector<double> result(n, -1.0); // Initialize result array with -1.0 (no collision)
6 stack<int> monotonic_stack; // Stack to maintain indices of cars in decreasing speed order
7
8 // Process cars from right to left (back to front)
9 for (int i = n - 1; i >= 0; --i) {
10 // Check potential collisions with cars ahead
11 while (!monotonic_stack.empty()) {
12 int j = monotonic_stack.top(); // Index of the car ahead
13
14 // Check if current car is faster than the car ahead
15 if (cars[i][1] > cars[j][1]) {
16 // Calculate collision time between car i and car j
17 double collision_time = static_cast<double>(cars[j][0] - cars[i][0]) /
18 (cars[i][1] - cars[j][1]);
19
20 // Check if this collision happens before car j collides with another car
21 // If car j doesn't collide with anyone (result[j] < 0) or
22 // if collision happens before car j's collision time
23 if (result[j] < 0 || collision_time <= result[j]) {
24 result[i] = collision_time;
25 break; // Found the collision time for car i
26 }
27 }
28 // Remove car j from stack as it won't affect cars further to the left
29 monotonic_stack.pop();
30 }
31
32 // Add current car to the stack for processing cars to its left
33 monotonic_stack.push(i);
34 }
35
36 return result;
37 }
38};
391function getCollisionTimes(cars: number[][]): number[] {
2 const n = cars.length;
3 const result: number[] = new Array(n).fill(-1.0); // Initialize result array with -1.0 (no collision)
4 const monotonicStack: number[] = []; // Stack to maintain indices of cars in decreasing speed order
5
6 // Process cars from right to left (back to front)
7 for (let i = n - 1; i >= 0; i--) {
8 // Check potential collisions with cars ahead
9 while (monotonicStack.length > 0) {
10 const j = monotonicStack[monotonicStack.length - 1]; // Index of the car ahead
11
12 // Check if current car is faster than the car ahead
13 if (cars[i][1] > cars[j][1]) {
14 // Calculate collision time between car i and car j
15 const collisionTime = (cars[j][0] - cars[i][0]) /
16 (cars[i][1] - cars[j][1]);
17
18 // Check if this collision happens before car j collides with another car
19 // If car j doesn't collide with anyone (result[j] < 0) or
20 // if collision happens before car j's collision time
21 if (result[j] < 0 || collisionTime <= result[j]) {
22 result[i] = collisionTime;
23 break; // Found the collision time for car i
24 }
25 }
26 // Remove car j from stack as it won't affect cars further to the left
27 monotonicStack.pop();
28 }
29
30 // Add current car to the stack for processing cars to its left
31 monotonicStack.push(i);
32 }
33
34 return result;
35}
36Time and Space Complexity
Time Complexity: O(n)
The algorithm iterates through all n cars once from right to left. Although there's a nested while loop that may pop elements from the stack, each car index is pushed onto the stack exactly once and popped at most once throughout the entire execution. This means the total number of operations across all iterations is bounded by 2n (each element can be pushed once and popped once), resulting in an amortized O(1) time per car and O(n) overall time complexity.
Space Complexity: O(n)
The algorithm uses:
- A stack
stkthat can contain at mostnelements (in the worst case, all car indices could be on the stack simultaneously) - An answer array
ansof sizento store the collision times
Both data structures require O(n) space, so the total space complexity is O(n).
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Collision Logic - Not Removing Unreachable Cars
The Pitfall: A common mistake is to check only if the current car is faster than the car ahead, without considering whether the car ahead might collide with another car before being reached. This leads to incorrect collision times.
Incorrect Implementation:
# WRONG: Only checks speed difference
for i in range(n - 1, -1, -1):
while stack:
j = stack[-1]
if cars[i][1] > cars[j][1]:
collision_time = (cars[j][0] - cars[i][0]) / (cars[i][1] - cars[j][1])
result[i] = collision_time
break # Wrong! Doesn't validate if this collision actually happens
stack.pop()
stack.append(i)
The Problem:
Car j might collide with another car at time t1, but car i might calculate it reaches car j at time t2 > t1. In reality, car i can't collide with car j after j has already collided with another car.
Correct Solution:
# Check if the collision happens before car j collides with another car if result[j] == -1 or collision_time <= result[j]: result[i] = collision_time break else: # Car j will collide before car i reaches it, so remove it stack.pop()
2. Division by Zero - Cars with Same Speed
The Pitfall: When two cars have the same speed, the collision time formula divides by zero:
collision_time = (cars[j][0] - cars[i][0]) / (cars[i][1] - cars[j][1]) # If cars[i][1] == cars[j][1], this causes division by zero!
Correct Solution: Always check that the current car is strictly faster before calculating collision time:
if cars[i][1] > cars[j][1]: # Ensures denominator is positive collision_time = (cars[j][0] - cars[i][0]) / (cars[i][1] - cars[j][1])
3. Stack Management - Forgetting to Pop Invalid Targets
The Pitfall: Not removing cars from the stack when they're unreachable can cause the algorithm to get stuck or produce wrong results.
Incorrect Pattern:
# WRONG: Breaks without popping unreachable cars while stack: j = stack[-1] if cars[i][1] <= cars[j][1]: break # Wrong! Should pop and continue
Correct Solution:
while stack: j = stack[-1] if cars[i][1] <= cars[j][1]: stack.pop() # Remove car j as it's not reachable continue # Check next car in stack
4. Processing Order - Iterating Left to Right
The Pitfall: Processing cars from left to right makes it impossible to know future collisions, leading to complex dependency chains.
Why Right to Left Works:
- The rightmost car has no car ahead, so its collision time is definitively
-1 - Each car only needs information about cars ahead (already processed)
- This creates a clean dependency order without circular references
Example to Illustrate:
# Cars: [[1,2], [3,1], [5,3]] # Processing right to left: # - Car at position 5: no collision (-1) # - Car at position 3: slower than car ahead, no collision (-1) # - Car at position 1: catches car at position 3 at time 1.0
You are given an array of intervals where intervals[i] = [start_i, end_i] represent the start and end of the ith interval. You need to merge all overlapping intervals and return an array of the non-overlapping intervals that cover all the intervals in the input.
Recommended Readings
Stack Intro New to stacks This article provides a quick review For a comprehensive beginner friendly lesson on stacks check out our Foundation Course Stack module courses foundation stack_lifo_model Imagine you have a pile of books on your desk If you want to add a new book you place it on top
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Monotonic Stack Deque Intro If you'd prefer a video div class responsive iframe iframe src https www youtube com embed Dq_ObZwTY_Q title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div The word monotonic means a list or
Want a Structured Path to Master System Design Too? Don’t Miss This!