853. Car Fleet

Cars travelling toward the same target merge into fleets when a faster car catches a slower one ahead. Sort by starting position, compute each car's arrival time, then use a monotonic stack of times to count how many distinct fleets reach the finish.

MediumMonotonic StackGreedySortingTypeScript

PROBLEM What we're solving

You have n cars on a one-lane road, each with a starting position and a speed. They all drive toward the same targetmile marker. A faster car that catches a slower one in front can't pass — they merge and travel together as a fleetat the slower car's speed. Return how many fleets arrive at the target.

Concrete example: target=12, position=[10,8,0,5,3], speed=[2,4,1,1,3].
Arrival times: car@10 → 1.0 hr, car@8 → 1.0 hr, car@5 → 7.0 hr, car@3 → 3.0 hr, car@0 → 12.0 hr. The car at 8 catches the car at 10 (same time → same fleet). The car at 3 can't catch the fleet at 5 (3.0 < 7.0 but it's behind and slower than the fleet). Three fleets arrive. Expected output: 3.

KEY IDEA Arrival time is all that matters

Insight → a car behind you can only catch you if its time-to-target is less than or equal to yours. Sort cars by starting position descending (closest to target first). Process them one by one: if the current car's arrival time is ≤ the fleet directly ahead, it gets absorbed — it can never pass. If it arrives later, it's slower overall and forms a new fleet. A monotonic stack of fleet times tracks this perfectly.

RECIPE Sort, compute times, stack

  • 0 · Pair and sort.Combine each car's position and speed, then sort by position descendingso we process cars from closest to target toward farthest. This ensures the "fleet ahead" is always at the top of the stack.
  • 1 · Compute arrival time. For each car, time = (target − pos) / spd. This is how many hours it would take if no blocking occurred.
  • 2 · Push or skip.If the stack is non-empty and the current car's time ≤ the top of the stack (the fleet ahead arrives no later), the current car gets blocked — continue(don't push). Otherwise it outruns the fleet ahead and starts a new fleet — push its time.
  • 3 · Return stack size. Every entry on the stack is one fleet.
Classic confusion → the condition is time <= stack[top], not <. When two cars have the exact same arrival time the rear car catches the front car right at the target — they count as one fleet. Using strict < would double-count that pair.

COST Complexity & alternatives

Simulate every step
O(n²)
Advance each car tick-by-tick, merge when they meet.
Sort + monotonic stack
O(n log n)
One sort, one linear pass; O(n) space for the stack.

Space note

The stack holds at most narrival times. In practice you don't even need the actual stack values — just the count and the time of the current top. Some solutions track only fleets and prevTime, reducing space to O(1) after sorting.

Pattern transfer →the same "sort by position, compare arrival times" pattern appears in Boats to Save People(two-pointer after sorting), and the monotonic-stack technique of "earlier element blocks the current one" underlies Daily Temperatures, Next Greater Element, and Largest Rectangle in Histogram.

RUN IT Sort by position, stack the arrival times

step 0 / 6
STARTSort 5cars by position descending. We'll process from closest to target → farthest, checking if each car catches the fleet ahead.
1function carFleet(target: number, position: number[], speed: number[]): number {
2 const n = position.length;
3 // Pair up and sort by starting position, closest to target first
4 const cars = position
5 .map((pos, i) => ({ pos, spd: speed[i] }))
6 .sort((a, b) => b.pos - a.pos);
7
8 // stack holds the arrival times of fleets (monotone increasing from front to back)
9 const stack: number[] = [];
10
11 for (const { pos, spd } of cars) {
12 const time = (target - pos) / spd;
13 // If this car arrives no later than the fleet in front, it gets blocked and joins it
14 if (stack.length > 0 && time <= stack[stack.length - 1]) continue;
15 // Otherwise it outruns the fleet ahead and forms a new one
16 stack.push(time);
17 }
18
19 return stack.length;
20}
p=10t=1.00p=8t=1.00p=5t=7.00p=3t=3.00p=0t=12.00
State
[]
stack
time
0
fleets
new fleet formedjoins fleet aheadprocessed / answer
slowfast

TYPESCRIPT The solution, annotated

carFleet.ts
function carFleet(target: number, position: number[], speed: number[]): number {
  const n = position.length;
  // Pair up and sort by starting position, closest to target first
  const cars = position
    .map((pos, i) => ({ pos, spd: speed[i] }))
    .sort((a, b) => b.pos - a.pos);

  // stack holds the arrival times of fleets (monotone increasing from front to back)
  const stack: number[] = [];

  for (const { pos, spd } of cars) {
    const time = (target - pos) / spd;
    // If this car arrives no later than the fleet in front, it gets blocked and joins it
    if (stack.length > 0 && time <= stack[stack.length - 1]) continue;
    // Otherwise it outruns the fleet ahead and forms a new one
    stack.push(time);
  }

  return stack.length;
}

Reading it block by block

Lines 3–5 — pair and sort.Zipping position and speed into objects before sorting keeps them coupled so we don't accidentally mix them up. Sorting descending by pos means the first car we process is the one closest to the target — the natural order for determining who blocks whom.
Line 8 — monotonic stack of arrival times.The stack will stay monotone increasing from front to back: each new entry is strictly greater than the one before it. That's the invariant — any car arriving earlier than the fleet ahead gets absorbed, so it never appears on the stack.
Lines 10–11 — compute time. (target - pos) / spd gives the unconstrained arrival time. Crucially, we compute this for the original speed; blocking is handled implicitly by the stack comparison, not by adjusting speeds.
Lines 12–13 — join or form.If the current car's time ≤ the top of the stack, it arrives no later than the fleet in front — it gets blocked and joins that fleet. We continue without pushing. Otherwise it travels faster overall and starts a new fleet; push its time.
Line 18 — return the count. Every element remaining on the stack represents one distinct fleet — a group of cars that travel together to the target. stack.length is the answer.
Complexity → O(n log n) time from the sort; the loop is O(n) because each car is pushed at most once. O(n) extra space for the stack (reducible to O(1) if you only track the previous fleet's time and a counter).

INTERVIEWFollow-ups they'll ask

  • "Can you use O(1) extra space?" Skip the explicit stack. Keep a prevTime variable and a fleets counter; update them in the same single pass after sorting.
  • "Return the sizes of each fleet, not just the count?" Push pairs of (time, count)onto the stack; increment the top's count when a car joins instead of skipping it.
  • "What if passing is allowed with a speed penalty?"The time-comparison trick breaks — you'd need a simulation or priority queue tracking each fleet's real position at each potential meeting point.
  • "Multiple lanes?" This becomes an entirely different problem (multi-lane merging) — sorting + stack no longer applies; typically modelled as a graph or interval-merge problem.
  • "Brute force?" Simulate in time increments: advance every car, merge adjacent ones that meet. O(n · T / dt) where T is the journey time — impractical for large inputs.

OPTIMAL Monotonic Stack

carFleet.ts
function carFleet(target: number, position: number[], speed: number[]): number {
  const n = position.length;
  // Pair up and sort by starting position, closest to target first
  const cars = position
    .map((pos, i) => ({ pos, spd: speed[i] }))
    .sort((a, b) => b.pos - a.pos);

  // stack holds the arrival times of fleets (monotone increasing from front to back)
  const stack: number[] = [];

  for (const { pos, spd } of cars) {
    const time = (target - pos) / spd;
    // If this car arrives no later than the fleet in front, it gets blocked and joins it
    if (stack.length > 0 && time <= stack[stack.length - 1]) continue;
    // Otherwise it outruns the fleet ahead and forms a new one
    stack.push(time);
  }

  return stack.length;
}
Complexity → O(n log n) time from the sort; the loop is O(n) because each car is pushed at most once. O(n) extra space for the stack (reducible to O(1) if you only track the previous fleet's time and a counter).

ALT 1 Brute force — rescan the cars ahead for each car

O(n²) time · O(n) space

Sort cars by position closest-to-target first and compute each arrival time. A car starts a new fleet only if its arrival time is strictly greater than the arrival time of every car ahead of it — so for each car, scan all the cars in front to check.

approach-2.ts
function carFleet(target: number, position: number[], speed: number[]): number {
  // Sort by position descending (closest to target first)
  const order = position
    .map((pos, i) => ({ pos, time: (target - pos) / speed[i] }))
    .sort((a, b) => b.pos - a.pos);

  let fleets = 0;
  for (let i = 0; i < order.length; i++) {
    // A car leads its own fleet unless some car ahead arrives at the same time or later
    let blocked = false;
    for (let j = 0; j < i; j++) {
      if (order[j].time >= order[i].time) { blocked = true; break; }
    }
    if (!blocked) fleets++;
  }
  return fleets;
}
Note → Checking every car against all the cars ahead of it is O(n²). The fix is the insight that you only ever need the slowest fleet directly in front: a single backward pass with a monotonic stack (or a running max arrival time) decides each car in O(1), giving O(n log n) dominated by the sort.

MNEMONIC The one-liner

"Closest car first, if you can't beat the fleet in front you join it — stack size is the answer."

TRIGGERS When you see ___ → reach for ___

cars merging when a faster one catches a slower onesort + monotonic stack of arrival times
"how many groups reach the finish"time-to-target comparison (blocked ⇒ join)
one-lane road, no passingrear car time ≤ front fleet time ⇒ merge
count distinct fleets / groups after mergingstack.length after single pass

SKELETON The reusable shape

skeleton.ts
function carFleet(target: number, position: number[], speed: number[]): number {
  const cars = position
    .map((pos, i) => ({ pos, spd: speed[i] }))
    .sort((a, b) => b.pos - a.pos);
  const stack: number[] = [];
  for (const { pos, spd } of cars) {
    const time = (target - pos) / spd;
    if (stack.length > 0 && time <= stack[stack.length - 1]) continue;
    stack.push(time);
  }
  return stack.length;
}

FLASHCARDS Tap to flip

Why sort by position descending?
So the car closest to the target is processed first — the stack top always represents the fleet directly ahead of the current car.
tap to flip
1 / 7
Answer to reveal explanations. No penalty for retries.Score: 0 / 9
QUESTION 01
target=12, position=[10,8,0,5,3], speed=[2,4,1,1,3]. How many fleets reach the target?
QUESTION 02
Why do we sort cars by starting position descending before processing?
QUESTION 03
A car at position 6 with speed 3 and target 12 has arrival time ___.
QUESTION 04
The comparison is time <= stack[top], not time < stack[top]. Why?
QUESTION 05
What is the time complexity of the optimal solution?
QUESTION 06
How can you solve Car Fleet in O(1) extra space (after the sort)?
QUESTION 07
After sorting descending and computing times, you get [2.0, 5.0, 3.0]. What does the stack look like after processing all three cars?
QUESTION 08
#853 · Car FleetSort cars by starting position descending and compute each car's time-to-target. A monotonic stack of arrival times detects fleets: a car that arrives no later than the fleet ahead merges into it.Which algorithmic approach does this primarily use?
QUESTION 09
#853 · Car FleetSort cars by starting position descending and compute each car's time-to-target. A monotonic stack of arrival times detects fleets: a car that arrives no later than the fleet ahead merges into it.Which implementation correctly solves it?