Skip to content

Commit 8a58640

Browse files
committed
Complete Chapter 11 (Greedy): jump games, meeting rooms, car fleet, task scheduler, refueling stops
1 parent 87446bf commit 8a58640

10 files changed

Lines changed: 1431 additions & 0 deletions

File tree

‎CodingInterviewFightClub/src/SUMMARY.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,13 @@
124124
- [10.5 First Unique Character](ch10-hash-tables/first-unique-character.md)
125125
- [10.6 Design HashMap](ch10-hash-tables/design-hash-map.md)
126126
- [10.7 Roman To Integer](ch10-hash-tables/roman-to-integer.md)
127+
128+
- [11. Greedy](ch11-greedy/index.md)
129+
- [11.0 Pattern Primer: The Local Choice, Defended](ch11-greedy/pattern-primer.md)
130+
- [11.1 Jump Game](ch11-greedy/jump-game.md)
131+
- [11.2 Jump Game II](ch11-greedy/jump-game-ii.md)
132+
- [11.3 Meeting Rooms](ch11-greedy/meeting-rooms.md)
133+
- [11.4 Meeting Rooms II](ch11-greedy/meeting-rooms-ii.md)
134+
- [11.5 Car Fleet](ch11-greedy/car-fleet.md)
135+
- [11.6 Task Scheduler](ch11-greedy/task-scheduler.md)
136+
- [11.7 Minimum Number Of Refueling Stops](ch11-greedy/minimum-number-of-refueling-stops.md)
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
# 11.5 Car Fleet
2+
3+
> **Source:** [`src/main/kotlin/greedy/CarFleet.kt`](https://github.com/arpanpathak/AdvancedAlgorithmPatterns/blob/main/src/main/kotlin/greedy/CarFleet.kt)
4+
> **Pattern:** sort by position + ETA sweep · **Core page**
5+
6+
## The Problem
7+
8+
`n` cars drive toward `target` at `position[i]` (all distinct) with `speed[i]`. A car **never passes** another — it catches up and forms a **fleet** that moves at the slower car's speed. Return the number of fleets that arrive at `target`.
9+
10+
- Constraints: $1 \le n \le 10^5$; `0 <= position[i] < target <= 10^6`.
11+
12+
## Examples
13+
14+
```
15+
Input: target = 12, position = [10,8,0,5,3], speed = [2,4,1,1,3]
16+
Output: 3 (fleets: {10}, {8,5,3} at speed 1, {0} — see trace)
17+
18+
Input: target = 10, position = [3], speed = [3]
19+
Output: 1
20+
```
21+
22+
## Intuition — "who catches whom" is decided by *arrival times*
23+
24+
Each car, alone, would reach the target at time `ETA(i) = (target - position[i]) / speed[i]` (the repo's `t = s / v` comment). A faster car *behind* a slower car will catch it before the target **iff its ETA is smaller** — and once caught, both arrive together at the *slower* car's ETA. So:
25+
26+
- sort cars by **position descending** (closest to target first);
27+
- walk that order, tracking the **slowest ETA seen so far** (the fleet leader's arrival time);
28+
- each car with ETA **larger** than the current leader's ETA starts a **new fleet** (it can't catch the fleet ahead — it would arrive later even alone);
29+
- a car with ETA **smaller or equal** merges into the fleet ahead (it catches it — same fleet, one count).
30+
31+
The answer is the number of times the running "slowest ETA" increases. **A car is a fleet leader iff its ETA is greater than every car ahead of it** — the greedy sweep counts exactly those records.
32+
33+
**Why sort by position, not ETA?** A car can only merge with the fleet *in front of it*. Position order defines "in front"; the ETA comparison decides "merge or not". The fleet structure is positional — hence the sort key.
34+
35+
**Floating-point equality is safe here** because the merge condition is `>` (strictly later = new fleet); a car with equal ETA arrives at the same moment, so it merges. No epsilon needed.
36+
37+
## Approach 1 — Simulate all cars pairwise (too slow)
38+
39+
For each pair, compute catch-up time and simulate merges: $O(n^2)$.
40+
41+
## Approach 2 — Sort + ETA sweep (the repo's version, optimal)
42+
43+
```kotlin
44+
class CarFleet {
45+
data class Car(val position: Double, val eta: Double)
46+
47+
/**
48+
* @param target destination distance
49+
* @param position position[i] of car i
50+
* @param speed speed[i] of car i
51+
* @return number of fleets reaching the target
52+
*/
53+
fun carFleet(target: Int, position: IntArray, speed: IntArray): Int {
54+
var (fleets, n) = listOf(0, position.size)
55+
val cars = mutableListOf<Car>()
56+
57+
// t = s / v (time to cover the remaining distance)
58+
position.forEachIndexed { i, pos ->
59+
cars.add(Car(pos.toDouble(), (target - pos).toDouble() / speed[i].toDouble()))
60+
}
61+
62+
// Sort by position descending: the car closest to target leads its fleet.
63+
cars.sortBy { -it.position }
64+
65+
var currentSlowestEta = 0.0
66+
cars.forEach { car ->
67+
// ETA larger than the current fleet leader -> catches nothing: new fleet
68+
if (car.eta > currentSlowestEta) {
69+
fleets++
70+
currentSlowestEta = car.eta
71+
}
72+
}
73+
return fleets
74+
}
75+
}
76+
```
77+
78+
```java
79+
import java.util.*;
80+
81+
public class CarFleet {
82+
/**
83+
* @param target destination distance
84+
* @param position position[i] of car i
85+
* @param speed speed[i] of car i
86+
* @return number of fleets reaching the target
87+
*/
88+
public int carFleet(int target, int[] position, int[] speed) {
89+
int n = position.length;
90+
double[][] cars = new double[n][2]; // {position, time to reach target}
91+
for (int i = 0; i < n; i++) {
92+
cars[i][0] = position[i];
93+
cars[i][1] = (double) (target - position[i]) / speed[i];
94+
}
95+
Arrays.sort(cars, (a, b) -> Double.compare(b[0], a[0])); // position descending
96+
97+
int fleets = 0;
98+
double slowest = 0;
99+
for (double[] car : cars) {
100+
if (car[1] > slowest) { // later than the fleet ahead: new fleet
101+
fleets++;
102+
slowest = car[1];
103+
}
104+
}
105+
return fleets;
106+
}
107+
}
108+
```
109+
110+
```cpp
111+
#include <algorithm>
112+
#include <vector>
113+
114+
class CarFleet {
115+
public:
116+
/**
117+
* @param target destination distance
118+
* @param position position[i] of car i
119+
* @param speed speed[i] of car i
120+
* @return number of fleets reaching the target
121+
*/
122+
int carFleet(int target, std::vector<int>& position, std::vector<int>& speed) {
123+
int n = position.size();
124+
std::vector<std::pair<int, double>> cars; // {position, time}
125+
for (int i = 0; i < n; i++) {
126+
cars.push_back({position[i], (double)(target - position[i]) / speed[i]});
127+
}
128+
std::sort(cars.begin(), cars.end(), // position descending
129+
[](const auto& a, const auto& b) { return a.first > b.first; });
130+
131+
int fleets = 0;
132+
double slowest = 0;
133+
for (auto& [_, eta] : cars) {
134+
if (eta > slowest) { // later than the fleet ahead: new fleet
135+
fleets++;
136+
slowest = eta;
137+
}
138+
}
139+
return fleets;
140+
}
141+
};
142+
```
143+
144+
```python
145+
def car_fleet(target: int, position: list[int], speed: list[int]) -> int:
146+
"""
147+
@param target: destination distance
148+
@param position: position[i] of car i
149+
@param speed: speed[i] of car i
150+
@return: number of fleets reaching the target
151+
"""
152+
cars = sorted(zip(position, speed), reverse=True) # position descending
153+
fleets = 0
154+
slowest = 0.0
155+
156+
for pos, spd in cars:
157+
eta = (target - pos) / spd
158+
if eta > slowest: # later than the fleet ahead: new fleet
159+
fleets += 1
160+
slowest = eta
161+
return fleets
162+
```
163+
164+
```rust
165+
impl Solution {
166+
/// @param target destination distance
167+
/// @param position position[i] of car i
168+
/// @param speed speed[i] of car i
169+
/// @return number of fleets reaching the target
170+
pub fn car_fleet(target: i32, position: Vec<i32>, speed: Vec<i32>) -> i32 {
171+
let mut cars: Vec<(i32, f64)> = position.iter().zip(speed.iter())
172+
.map(|(&p, &s)| (p, (target - p) as f64 / s as f64))
173+
.collect();
174+
cars.sort_by(|a, b| b.0.cmp(&a.0)); // position descending
175+
176+
let mut fleets = 0;
177+
let mut slowest = 0.0f64;
178+
for (_, eta) in cars {
179+
if eta > slowest { // later than the fleet ahead: new fleet
180+
fleets += 1;
181+
slowest = eta;
182+
}
183+
}
184+
fleets
185+
}
186+
}
187+
```
188+
189+
## Dry run
190+
191+
**Input:** `target = 12`, `position = [10,8,0,5,3]`, `speed = [2,4,1,1,3]`.
192+
193+
```
194+
ETAs (12 - pos) / speed: car@10: 2/2=1, car@8: 4/4=1, car@5: 7/1=7, car@3: 9/3=3, car@0: 12/1=12
195+
196+
cars sorted by position descending: (10,1), (8,1), (5,7), (3,3), (0,12)
197+
198+
fleets=0, slowest=0
199+
(10,1): 1 > 0 -> fleet! fleets=1, slowest=1
200+
(8,1): 1 > 1? no -> merges into the fleet ahead (same arrival time 1). fleets=1
201+
(5,7): 7 > 1 -> fleet! fleets=2, slowest=7
202+
(3,3): 3 > 7? no -> catches the (5) fleet, arriving at 7 together. fleets=2
203+
(0,12): 12 > 7 -> fleet! fleets=3, slowest=12
204+
205+
Output: 3 ✓
206+
```
207+
208+
The two merge lines are the physical intuition: car@8 catches car@10 *immediately* (same ETA), and car@3 is slower than the fleet at 5 — it catches *it* (moving at the fleet's slower speed), not the other way around. A car becomes a leader only when it's faster than everything ahead — which is exactly the "new record in the ETA sweep" condition.
209+
210+
## Complexity
211+
212+
**Time.** Sort dominates:
213+
214+
$$
215+
T(n) = O(n \log n)
216+
$$
217+
218+
**Space.** The car list:
219+
220+
$$
221+
S(n) = O(n)
222+
$$
223+
224+
## Variants & follow-ups
225+
226+
- **Car Fleet II** — *collision times* (when fleets form) instead of arrival counts: a monotonic stack over ETAs, the [Chapter 8](../ch08-stacks/index.md) engine wearing a physics costume.
227+
- **Maximum Profit Assigning Work** (`src/main/kotlin/greedy/MaxProfiAssigningWork.kt`) — the same "sort two axes, sweep one" shape.
228+
- **Interview follow-up:** "Why is a car's own speed irrelevant once it merges?" The fleet moves at the *slowest* member's speed — the leader's ETA — so after the merge decision, the faster car's speed is never consulted again. That's why the sweep only tracks `slowest` (the fleet leader's ETA), not every car's.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Chapter 11 — Greedy
2+
3+
> **Source:** `src/main/kotlin/greedy/`
4+
>
5+
> **Master idea:** a greedy algorithm makes the **locally optimal choice at every step** — and is *correct* only when the local choice can be proven globally optimal. This chapter's problems fall into three moves: *reach/frontier tracking*, *interval scheduling by sorting*, and *deferred decisions with a heap*.
6+
>
7+
> **Prerequisites:** sorting, the heap from [Chapter 7](../ch07-heaps/index.md), and a habit of asking "but does greedy actually work here?" — the answer is never obvious, it's proven.
8+
9+
## Problems at a glance (this chapter's core set)
10+
11+
| # | Problem | Pattern | Complexity | Page |
12+
|---|---------|---------|------------|------|
13+
| 11.1 | Jump Game | reachable-frontier tracking | $O(n)$ | [→](jump-game.md) |
14+
| 11.2 | Jump Game II | frontier + jump count | $O(n)$ | [→](jump-game-ii.md) |
15+
| 11.3 | Meeting Rooms | sort + adjacency check | $O(n \log n)$ | [→](meeting-rooms.md) |
16+
| 11.4 | Meeting Rooms II | sort + min-heap of end times | $O(n \log n)$ | [→](meeting-rooms-ii.md) |
17+
| 11.5 | Car Fleet | sort by position + ETA sweep | $O(n \log n)$ | [→](car-fleet.md) |
18+
| 11.6 | Task Scheduler | frequency math | $O(n)$ | [→](task-scheduler.md) |
19+
| 11.7 | Minimum Number Of Refueling Stops | max-heap "time travel" | $O(n \log n)$ | [→](minimum-number-of-refueling-stops.md) |
20+
21+
## The rest of the greedy/ directory
22+
23+
`src/main/kotlin/greedy/` also holds: Destroying Asteroids (greedy by size), Jump Game variants, Maximum Profit Assigning Work (sorted pointers), Minimum Time To Make Rope Colorful (keep the max per run), Minimum Deletions To Make String Balanced, Minimum Replacement To Sort The Array, Reschedule Meetings For Maximum Free Time, Maximum Value Of An Ordered Triplet II, Max Chunks To Make Sorted II, MInimum Cost Homecoming Of A Robot, and Task Scheduler neighbors. The interval-family problems connect to `src/main/kotlin/interval/` and the scheduling problems to [7.5](../ch07-heaps/ipo.md)/[7.6](../ch07-heaps/meeting-rooms-iii.md) from the heap chapter.
24+
25+
New pages are appended to the table above as they're written.

0 commit comments

Comments
 (0)