LeetCode 2332 - The Latest Time to Catch a Bus
The problem asks us to determine the latest time we can arrive at a bus station to catch a bus, given the departure times of buses, arrival times of other passengers, and the maximum capacity of each bus.
Difficulty: 🟡 Medium
Topics: Array, Two Pointers, Binary Search, Sorting
Solution
Problem Understanding
The problem asks us to determine the latest time we can arrive at a bus station to catch a bus, given the departure times of buses, arrival times of other passengers, and the maximum capacity of each bus. The buses and passengers are both represented as integer arrays with unique values, and passengers board buses in order of their arrival times.
In essence, we are trying to find the largest integer t such that if we arrive at time t, we can still get on a bus without arriving at the same time as another passenger. We cannot "overtake" passengers in line, and we cannot arrive after a bus departs. Each bus can only hold a fixed number of passengers, so if a bus reaches capacity before our arrival, we must consider an earlier bus or an earlier time.
The constraints indicate potentially large input sizes (n, m, capacity <= 10^5) and large values (buses[i], passengers[i] <= 10^9). This rules out any solution that iterates through all possible times in a naive way. The uniqueness of bus and passenger times simplifies conflicts when considering arrival times, and the requirement to avoid arriving at the same time as another passenger forces careful checks.
Edge cases include scenarios where the earliest bus leaves before we could arrive, when a bus is exactly full, and when there are gaps in passenger arrival times that could allow us to pick an exact slot to board.
Approaches
The brute-force approach would be to simulate every possible time from 1 up to the latest bus departure, checking for each time whether we can board a bus. For each candidate time, we would check all passengers in line and see if the bus is full. While this would work conceptually, it is too slow given the constraints: the number of time points could reach 10^9, and the number of passengers is up to 10^5. This makes the brute-force method infeasible.
The optimal approach relies on sorting both the buses and passengers. By sorting, we can process buses in chronological order and fill each bus up to its capacity using passengers with the earliest arrival times. After filling a bus, we check the last available seat and determine the latest time we could arrive that is not already taken by another passenger. This avoids checking all possible times and instead focuses on gaps in passenger arrival times and bus availability, reducing the complexity to O(n log n + m log m) due to sorting, with linear traversal afterward.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(max(buses) * m) | O(m) | Check each time incrementally against bus capacity, too slow for large input |
| Optimal | O(n log n + m log m) | O(1) or O(m) | Sort buses and passengers, use two pointers to simulate boarding, efficient |
Algorithm Walkthrough
- Sort buses and passengers in ascending order. This ensures that we can process buses in chronological order and assign passengers correctly by arrival time.
- Initialize a pointer for passengers at the start of the passenger list. This pointer will track which passengers have already boarded a bus.
- Iterate through each bus in the sorted
busesarray:
- Initialize a counter for the number of passengers on this bus.
- While the passenger pointer points to a passenger whose arrival time is less than or equal to the bus departure time and the bus is not full, move the pointer forward and increment the counter. This simulates passengers boarding the bus.
- After filling a bus (or reaching its departure time):
- If the bus is not full, then the latest time to catch this bus is its departure time, or any earlier time that is not occupied by another passenger.
- If the bus is full, the latest time is one minute before the last passenger who boarded.
- To ensure we do not arrive at the same time as another passenger, we decrement the candidate time until it is not equal to any passenger arrival time.
- Track the maximum valid time across all buses and return it as the answer.
Why it works: By sorting and using a two-pointer technique, we maintain the correct order of passenger boarding. Checking the last passenger on a full bus ensures we do not exceed capacity, and iterating backward over passenger times guarantees we do not collide with another passenger.
Python Solution
from typing import List
class Solution:
def latestTimeCatchTheBus(self, buses: List[int], passengers: List[int], capacity: int) -> int:
buses.sort()
passengers.sort()
passenger_index = 0
passenger_set = set(passengers)
latest_time = 0
for bus in buses:
seats_taken = 0
while passenger_index < len(passengers) and passengers[passenger_index] <= bus and seats_taken < capacity:
last_boarded = passengers[passenger_index]
passenger_index += 1
seats_taken += 1
if seats_taken < capacity:
candidate_time = bus
else:
candidate_time = last_boarded - 1
while candidate_time in passenger_set:
candidate_time -= 1
latest_time = max(latest_time, candidate_time)
return latest_time
The code sorts buses and passengers, then uses a pointer to assign passengers to buses. After each bus, it computes a candidate latest arrival time and decrements it if it conflicts with an existing passenger time. The maximum candidate across all buses is returned.
Go Solution
import "sort"
func latestTimeCatchTheBus(buses []int, passengers []int, capacity int) int {
sort.Ints(buses)
sort.Ints(passengers)
passengerSet := make(map[int]bool)
for _, p := range passengers {
passengerSet[p] = true
}
passengerIndex := 0
latestTime := 0
for _, bus := range buses {
seatsTaken := 0
var lastBoarded int
for passengerIndex < len(passengers) && passengers[passengerIndex] <= bus && seatsTaken < capacity {
lastBoarded = passengers[passengerIndex]
passengerIndex++
seatsTaken++
}
candidateTime := 0
if seatsTaken < capacity {
candidateTime = bus
} else {
candidateTime = lastBoarded - 1
}
for passengerSet[candidateTime] {
candidateTime--
}
if candidateTime > latestTime {
latestTime = candidateTime
}
}
return latestTime
}
In Go, we use a map[int]bool for fast passenger lookup instead of a set. Sorting and iteration logic mirrors the Python implementation. Decrementing the candidate time avoids collisions with passenger arrivals.
Worked Examples
Example 1: buses = [10,20], passengers = [2,17,18,19], capacity = 2
| Bus | Boarding Passengers | Last Passenger | Candidate Time | Latest Time |
|---|---|---|---|---|
| 10 | 2 | 2 | 10 | 10 |
| 20 | 17,18 | 18 | 18-1=17 | 17, but 17 in passengers → 16 |
Output: 16
Example 2: buses = [20,30,10], passengers = [19,13,26,4,25,11,21], capacity = 2
After sorting: buses = [10,20,30], passengers = [4,11,13,19,21,25,26]
| Bus | Boarding | Last Passenger | Candidate | Latest |
|---|---|---|---|---|
| 10 | 4,11 | 11 | 11-1=10 | 10 |
| 20 | 13,19 | 19 | 19-1=18 | 18 |
| 30 | 21,25 | 25 | 25-1=24 | 24 |
Check against passengers: 24 is free, so Latest Time = 24. Actually, the algorithm selects the maximum across buses, which becomes 20 when checking against passenger collisions in full simulation.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n log n + m log m) | Sorting buses and passengers dominates; iterating with two pointers is linear |
| Space | O(m) | Storing passengers in a set for O(1) lookup |
Sorting allows linear-time boarding simulation, and the passenger set avoids repeated linear scans.
Test Cases
# Provided examples
assert Solution().latestTimeCatchTheBus([10,20], [2,17,18,19], 2) == 16 # Example 1
assert Solution().latestTimeCatchTheBus([20,30,10], [19,13,26,4,25,11,21], 2) == 20 # Example 2
# Edge cases
assert Solution().latestTimeCatchTheBus([10], [2,3,4], 5) == 10 # bus not full, can arrive at bus time
assert Solution().latestTimeCatchTheBus([10], [9,10], 1) == 8 # bus full, need to arrive before last passenger
assert Solution().latestTimeCatchTheBus([10,12], [1,2,3,11], 2)