Design Ride Sharing System solution and explanation
4 more videos on this problem
Design Ride Sharing System | LeetCode 3829 | Weekly Contest 487Sanyam IIT Guwahati · 24:11 · 522 views
3829. Design Ride Sharing System (Leetcode Medium)Programming Live with Larry · 8:26 · 185 views
Leetcode 3828 | Leetcode 3829 | Design Ride Sharing System | Final Element After Subarray DeletionsCodeWithMeGuys · 16:43 · 171 views
LeetCode Problem 3829 | Design Ride Sharing SystemRepovive TV · 11:45 · 79 views
Weekly Contest 487 - Leetcode 3829. Design Ride Sharing SystemCode Maestro Rtj · 10:36 · 79 views
The problem
A ride sharing system manages ride requests from riders and availability from drivers. Riders request rides, and drivers become available over time. The system should match riders and drivers in the order they arrive.
Implement the RideSharingSystem class:
RideSharingSystem()Initializes the system.void addRider(int riderId)Adds a new rider with the givenriderId.void addDriver(int driverId)Adds a new driver with the givendriverId.int[] matchDriverWithRider()Matches the earliest available driver with the earliest waiting rider and removes both of them from the system. Returns an integer array of size 2 whereresult = [driverId, riderId]if a match is made. If no match is available, returns[-1, -1].void cancelRider(int riderId)Cancels the ride request of the rider with the givenriderIdif the rider exists and has not yet been matched.
Example 1:
Input:
["RideSharingSystem", "addRider", "addDriver", "addRider", "matchDriverWithRider", "addDriver", "cancelRider", "matchDriverWithRider", "matchDriverWithRider"]
[[], [3], [2], [1], [], [5], [3], [], []]
Output:
[null, null, null, null, [2, 3], null, null, [5, 1], [-1, -1]]
Explanation
RideSharingSystem rideSharingSystem = new RideSharingSystem(); // Initializes the systemrideSharingSystem.addRider(3); // rider 3 joins the queue
rideSharingSystem.addDriver(2); // driver 2 joins the queue
rideSharingSystem.addRider(1); // rider 1 joins the queue
rideSharingSystem.matchDriverWithRider(); // returns [2, 3]
rideSharingSystem.addDriver(5); // driver 5 becomes available
rideSharingSystem.cancelRider(3); // rider 3 is already matched, cancel has no effect
rideSharingSystem.matchDriverWithRider(); // returns [5, 1]
rideSharingSystem.matchDriverWithRider(); // returns [-1, -1]
Example 2:
Input:
["RideSharingSystem", "addRider", "addDriver", "addDriver", "matchDriverWithRider", "addRider", "cancelRider", "matchDriverWithRider"]
[[], [8], [8], [6], [], [2], [2], []]
Output:
[null, null, null, null, [8, 8], null, null, [-1, -1]]
Explanation
RideSharingSystem rideSharingSystem = new RideSharingSystem(); // Initializes the systemrideSharingSystem.addRider(8); // rider 8 joins the queue
rideSharingSystem.addDriver(8); // driver 8 joins the queue
rideSharingSystem.addDriver(6); // driver 6 joins the queue
rideSharingSystem.matchDriverWithRider(); // returns [8, 8]
rideSharingSystem.addRider(2); // rider 2 joins the queue
rideSharingSystem.cancelRider(2); // rider 2 cancels
rideSharingSystem.matchDriverWithRider(); // returns [-1, -1]
Constraints:
1 <= riderId, driverId <= 1000- Each
riderIdis unique among riders and is added at most once. - Each
driverIdis unique among drivers and is added at most once. - At most 1000 calls will be made in total to
addRider,addDriver,matchDriverWithRider, andcancelRider.
Approach overview
Problem Overview: Design a ride sharing system that processes a continuous stream of ride requests and driver availability updates. The system must efficiently match riders with drivers while maintaining fast lookups and ordered processing of requests.
Approach 1: Queue Simulation + Linear Search (O(n) per operation, O(n) space)
The straightforward implementation uses a queue to store incoming ride requests and a list or map of available drivers. Each time a request arrives, you iterate through the driver list to find a suitable match. When a driver becomes available, you scan the queued requests to assign the earliest compatible rider. This approach mirrors the real-world first-come-first-served process but requires scanning potentially many elements for each assignment.
The key operations are queue insertion for incoming requests and linear iteration for matching. While simple to implement, the repeated scans cause O(n) time per match operation, which becomes slow as the number of riders and drivers grows. Space complexity remains O(n) because all active requests and drivers must be stored in memory.
Approach 2: Sorted Set + Hash Table (O(log n) per operation, O(n) space)
A more scalable design combines a sorted structure for ordering with a hash table for constant-time lookups. The hash table maps rider or driver IDs to their current state, while a sorted set maintains ordering based on attributes such as request time, priority, or distance. This allows the system to always retrieve the most suitable match without scanning the entire dataset.
When a new ride request arrives, insert it into the sorted set and record its metadata in the hash table. When a driver becomes available, query the sorted set to fetch the best candidate in O(log n) time. After a match, remove both entities from the sorted structure and update the hash table. This design supports fast updates and efficient matching even under heavy request streams, which is typical in data stream style problems.
The combination works because the sorted set guarantees efficient ordering while the hash table provides direct access to objects during updates or cancellations. Insertions, deletions, and queries all run in O(log n) time, making the system responsive even with thousands of active rides.
Recommended for interviews: Interviewers typically expect the Sorted Set + Hash Table design. A basic queue simulation shows you understand the workflow, but the optimized design demonstrates knowledge of scalable system design and efficient data structures. Mentioning why linear scans fail at scale and replacing them with ordered retrieval is the key insight that signals strong problem-solving ability.
Solution
We use two sorted sets riders and drivers to store waiting riders and available drivers respectively. Each element is a tuple (t, id), representing the ID of the rider/driver and their timestamp t when they joined the system. The timestamp t is used to distinguish the order of arrival. Initially, t = 0, and each time a rider or driver is added, t is incremented by 1.
Additionally, we use a hash table d to store the mapping between each rider's ID and their timestamp, which facilitates lookup when canceling a rider's request.
Specifically:
- When adding a rider, we add
(t, riderId)toriders, setd[riderId] = t, and then incrementtby1. - When adding a driver, we add
(t, driverId)todriversand then incrementtby1. - When matching a driver with a rider, if either
ridersordriversis empty, we return[-1, -1]. Otherwise, we remove the elements with the smallest timestamps from bothridersanddrivers, namely(t_r, riderId)and(t_d, driverId), and return[driverId, riderId]. - When canceling a rider's request, we look up the rider's timestamp
tthroughd, and then remove(t, riderId)fromriders.
The time complexity is O(log n) per operation, where n is the current number of riders or drivers. The space complexity is O(n).
class RideSharingSystem:
def __init__(self):
self.t = 0
self.riders = SortedList()
self.drivers = SortedList()
self.d = defaultdict(int)
def addRider(self, riderId: int) -> None:
self.d[riderId] = self.t
self.riders.add((self.t, riderId))
self.t += 1
def addDriver(self, driverId: int) -> None:
self.drivers.add((self.t, driverId))
self.t += 1
def matchDriverWithRider(self) -> List[int]:
if len(self.riders) < 1 or len(self.drivers) < 1:
return [-1, -1]
return [self.drivers.pop(0)[1], self.riders.pop(0)[1]]
def cancelRider(self, riderId: int) -> None:
self.riders.discard((self.d[riderId], riderId))
# Your RideSharingSystem object will be instantiated and called as such:
# obj = RideSharingSystem()
# obj.addRider(riderId)
# obj.addDriver(driverId)
# param_3 = obj.matchDriverWithRider()
# obj.cancelRider(riderId)class RideSharingSystem {
private int t;
private TreeSet<int[]> riders;
private TreeSet<int[]> drivers;
private Map<Integer, Integer> d;
public RideSharingSystem() {
this.t = 0;
this.riders = new TreeSet<>(
(a, b) -> a[0] != b[0] ? Integer.compare(a[0], b[0]) : Integer.compare(a[1], b[1]));
this.drivers = new TreeSet<>(
(a, b) -> a[0] != b[0] ? Integer.compare(a[0], b[0]) : Integer.compare(a[1], b[1]));
this.d = new HashMap<>();
}
public void addRider(int riderId) {
d.put(riderId, t);
riders.add(new int[] {t, riderId});
t++;
}
public void addDriver(int driverId) {
drivers.add(new int[] {t, driverId});
t++;
}
public int[] matchDriverWithRider() {
if (riders.isEmpty() || drivers.isEmpty()) {
return new int[] {-1, -1};
}
int driverId = drivers.pollFirst()[1];
int riderId = riders.pollFirst()[1];
return new int[] {driverId, riderId};
}
public void cancelRider(int riderId) {
Integer time = d.get(riderId);
if (time != null) {
riders.remove(new int[] {time, riderId});
}
}
}
/**
* Your RideSharingSystem object will be instantiated and called as such:
* RideSharingSystem obj = new RideSharingSystem();
* obj.addRider(riderId);
* obj.addDriver(driverId);
* int[] param_3 = obj.matchDriverWithRider();
* obj.cancelRider(riderId);
*/class RideSharingSystem {
private:
int t;
set<pair<int, int>> riders;
set<pair<int, int>> drivers;
unordered_map<int, int> d;
public:
RideSharingSystem() {
t = 0;
}
void addRider(int riderId) {
d[riderId] = t;
riders.insert({t, riderId});
t++;
}
void addDriver(int driverId) {
drivers.insert({t, driverId});
t++;
}
vector<int> matchDriverWithRider() {
if (riders.empty() || drivers.empty()) {
return {-1, -1};
}
int driverId = drivers.begin()->second;
int riderId = riders.begin()->second;
drivers.erase(drivers.begin());
riders.erase(riders.begin());
return {driverId, riderId};
}
void cancelRider(int riderId) {
auto it = d.find(riderId);
if (it != d.end()) {
riders.erase({it->second, riderId});
}
}
};
/**
* Your RideSharingSystem object will be instantiated and called as such:
* RideSharingSystem* obj = new RideSharingSystem();
* obj->addRider(riderId);
* obj->addDriver(driverId);
* vector<int> param_3 = obj->matchDriverWithRider();
* obj->cancelRider(riderId);
*/type RideSharingSystem struct {
t int
riders *redblacktree.Tree[int, int]
drivers *redblacktree.Tree[int, int]
d map[int]int
}
func Constructor() RideSharingSystem {
return RideSharingSystem{
t: 0,
riders: redblacktree.New[int, int](),
drivers: redblacktree.New[int, int](),
d: make(map[int]int),
}
}
func (this *RideSharingSystem) AddRider(riderId int) {
this.d[riderId] = this.t
this.riders.Put(this.t, riderId)
this.t++
}
func (this *RideSharingSystem) AddDriver(driverId int) {
this.drivers.Put(this.t, driverId)
this.t++
}
func (this *RideSharingSystem) MatchDriverWithRider() []int {
if this.riders.Empty() || this.drivers.Empty() {
return []int{-1, -1}
}
driverTime, driverId := this.drivers.Left().Key, this.drivers.Left().Value
riderTime, riderId := this.riders.Left().Key, this.riders.Left().Value
this.drivers.Remove(driverTime)
this.riders.Remove(riderTime)
return []int{driverId, riderId}
}
func (this *RideSharingSystem) CancelRider(riderId int) {
time, exists := this.d[riderId]
if !exists {
return
}
this.riders.Remove(time)
}
/**
* Your RideSharingSystem object will be instantiated and called as such:
* obj := Constructor();
* obj.AddRider(riderId);
* obj.AddDriver(driverId);
* param_3 := obj.MatchDriverWithRider();
* obj.CancelRider(riderId);
*/Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Queue Simulation + Linear Search | O(n) per match | O(n) | Simple prototype or when the number of drivers and riders is very small |
| Sorted Set + Hash Table | O(log n) per operation | O(n) | Production-scale systems or interview solutions requiring efficient matching |
Frequently asked questions
Is Design Ride Sharing System easy or hard?
Design Ride Sharing System is typically classified as a medium difficulty problem. The main challenge is choosing the right combination of data structures to support efficient matching and updates in a streaming environment.
Design Ride Sharing System Python/Java solution
Implement the optimized solution using a hash map for rider or driver state and a sorted container such as TreeSet in Java, SortedList in Python, or ordered sets in C++/Go. Each request or availability update inserts or removes entries while maintaining sorted order for efficient matching.
How to solve Design Ride Sharing System in O(log n)?
Maintain ride requests in a sorted structure such as a balanced tree or ordered set keyed by request priority or time. Store driver and rider metadata in a hash map. When a driver becomes available, query the sorted set to retrieve the best rider in O(log n), then update both structures after the match.
What is the best approach for Design Ride Sharing System?
The most efficient solution uses a combination of a sorted set and a hash table. The sorted set maintains ordered ride requests or driver availability, while the hash table provides O(1) lookups for driver or rider metadata. Each insertion, deletion, and match operation runs in O(log n) time with O(n) total space.
Is Design Ride Sharing System asked at Google/Amazon/Meta?
Ride sharing system design and streaming matching problems frequently appear in interviews at companies like Uber, Lyft, Amazon, and Google. These questions test your ability to combine data structures such as heaps, ordered sets, queues, and hash maps for scalable real-time systems.
What data structure is used in Design Ride Sharing System?
The common implementation uses a hash table for quick lookups and a sorted set or balanced tree for ordered retrieval of ride requests or drivers. Queues may also appear in simpler designs that process requests in arrival order.
What is the time complexity of Design Ride Sharing System?
The optimized Sorted Set + Hash Table approach performs insert, remove, and match operations in O(log n) time. Space complexity is O(n) because all active riders and drivers must be stored in memory structures for quick access.
