Taro Logo

Design Underground System

Medium
Google logo
Google
1 view

Let's design an underground railway system to track customer travel times between stations. We want to calculate the average travel time between any two given stations.

Implement the UndergroundSystem class with the following methods:

  1. void checkIn(int id, string stationName, int t): A customer with card ID id checks in at stationName at time t. A customer can only be checked into one place at a time.

  2. void checkOut(int id, string stationName, int t): A customer with card ID id checks out from stationName at time t.

  3. double getAverageTime(string startStation, string endStation): Returns the average time it takes to travel from startStation to endStation. This is computed from all previous trips directly from startStation to endStation. Assume at least one customer has traveled from startStation to endStation before this method is called, and travel time may differ in each direction.

Example:

UndergroundSystem undergroundSystem = new UndergroundSystem();
undergroundSystem.checkIn(45, "Leyton", 3);
undergroundSystem.checkIn(32, "Paradise", 8);
undergroundSystem.checkIn(27, "Leyton", 10);
undergroundSystem.checkOut(45, "Waterloo", 15);  // Customer 45: Leyton -> Waterloo in 12
undergroundSystem.checkOut(27, "Waterloo", 20);  // Customer 27: Leyton -> Waterloo in 10
undergroundSystem.checkOut(32, "Cambridge", 22); // Customer 32: Paradise -> Cambridge in 14

undergroundSystem.getAverageTime("Paradise", "Cambridge"); // Returns 14.0
undergroundSystem.getAverageTime("Leyton", "Waterloo");    // Returns 11.0

undergroundSystem.checkIn(10, "Leyton", 24);
undergroundSystem.getAverageTime("Leyton", "Waterloo");    // Returns 11.0
undergroundSystem.checkOut(10, "Waterloo", 38);  // Customer 10: Leyton -> Waterloo in 14
undergroundSystem.getAverageTime("Leyton", "Waterloo");    // Returns 12.0

How would you implement this system to efficiently compute average travel times?

Solution


Naive Approach

A naive approach would be to store all travel times for each route in a list. When getAverageTime is called, we would simply calculate the average of the travel times in the list. This is straightforward but can be inefficient if there are many trips.

Implementation

class UndergroundSystem:
    def __init__(self):
        self.check_in_data = {}
        self.route_times = {}

    def checkIn(self, id: int, stationName: str, t: int) -> None:
        self.check_in_data[id] = (stationName, t)

    def checkOut(self, id: int, stationName: str, t: int) -> None:
        start_station, start_time = self.check_in_data[id]
        del self.check_in_data[id]
        route = (start_station, stationName)
        travel_time = t - start_time
        if route not in self.route_times:
            self.route_times[route] = []
        self.route_times[route].append(travel_time)

    def getAverageTime(self, startStation: str, endStation: str) -> float:
        route = (startStation, endStation)
        times = self.route_times[route]
        return sum(times) / len(times)

Time and Space Complexity

  • Time Complexity:
    • checkIn: O(1)
    • checkOut: O(1)
    • getAverageTime: O(N), where N is the number of trips between the two stations.
  • Space Complexity: O(M), where M is the total number of trips made in the underground system. This is because we are storing all the travel times.

Optimal Approach

To improve efficiency, we can maintain running sums and counts of travel times for each route. This avoids iterating through a potentially large list every time getAverageTime is called.

Implementation

class UndergroundSystem:
    def __init__(self):
        self.check_in_data = {}
        self.route_data = {}

    def checkIn(self, id: int, stationName: str, t: int) -> None:
        self.check_in_data[id] = (stationName, t)

    def checkOut(self, id: int, stationName: str, t: int) -> None:
        start_station, start_time = self.check_in_data[id]
        del self.check_in_data[id]
        end_station = stationName
        travel_time = t - start_time
        route = (start_station, end_station)
        if route not in self.route_data:
            self.route_data[route] = [0, 0]
        self.route_data[route][0] += travel_time
        self.route_data[route][1] += 1

    def getAverageTime(self, startStation: str, endStation: str) -> float:
        route = (startStation, endStation)
        total_time, count = self.route_data[route]
        return total_time / count

Time and Space Complexity

  • Time Complexity:
    • checkIn: O(1)
    • checkOut: O(1)
    • getAverageTime: O(1)
  • Space Complexity: O(R), where R is the number of distinct routes. We only store a sum and a count for each route.

Edge Cases

  • No Trips: The problem statement guarantees that there will be at least one trip between any two stations for which getAverageTime is called. Thus, we do not need to handle this edge case explicitly.
  • Invalid Input: The problem statement assumes that all input is valid. We don't need to add explicit checks for invalid station names or times.