Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1396. Design Underground System - Leetcode Solution

Problem Description

The "Design Underground System" problem involves tracking the travel times of passengers in an underground transit system. You are required to design a class UndergroundSystem that supports the following operations:

  • checkIn(id, stationName, t): A customer with a unique id checks in at stationName at time t.
  • checkOut(id, stationName, t): The same customer checks out from stationName at time t.
  • getAverageTime(startStation, endStation): Returns the average time taken by all customers who traveled from startStation to endStation.

Each customer can only be checked into one station at a time. The system should efficiently support multiple queries and updates. The solution must ensure that each trip is only counted once, and the average is calculated only for valid completed trips between the specified stations.

Thought Process

At first glance, a brute-force approach might involve storing each trip individually and iterating through all trips to compute averages. However, this would be inefficient, especially as the number of trips grows.

Instead, we should focus on using data structures that allow us to:

  • Quickly record check-ins and check-outs for each customer.
  • Efficiently compute and update the running totals and counts for each station pair.
  • Return the average in constant time for any station pair.

This leads us to use hash maps (dictionaries) to associate customer IDs with their current journey, and another hash map to track total times and trip counts for each station pair.

Solution Approach

To design the UndergroundSystem efficiently, we use two main hash maps:

  • Check-in Map:
    • Key: id of the customer
    • Value: Tuple of (stationName, checkInTime)
    • This allows us to quickly look up where and when a customer started their journey.
  • Travel Data Map:
    • Key: Tuple (startStation, endStation)
    • Value: Tuple (totalTravelTime, tripCount)
    • This allows us to maintain the running total and count for each station pair.
  1. On checkIn(id, stationName, t):
    • Store the stationName and t in the check-in map for this id.
  2. On checkOut(id, stationName, t):
    • Retrieve the check-in data for this id.
    • Calculate the trip duration as t - checkInTime.
    • Update the travel data map for (startStation, endStation) by adding the duration to the total and incrementing the count.
    • Remove the id from the check-in map (since the trip is completed).
  3. On getAverageTime(startStation, endStation):
    • Retrieve the total time and trip count for the station pair.
    • Return the average as totalTime / tripCount.

This approach ensures all operations are efficient and the data is always consistent.

Example Walkthrough

Let's step through a sample sequence of operations:

  1. checkIn(45, "Leyton", 3)
  2. checkIn(32, "Paradise", 8)
  3. checkIn(27, "Leyton", 10)
  4. checkOut(45, "Waterloo", 15)
    - Customer 45's trip: "Leyton" to "Waterloo", duration = 15 - 3 = 12.
  5. checkOut(27, "Waterloo", 20)
    - Customer 27's trip: "Leyton" to "Waterloo", duration = 20 - 10 = 10.
  6. checkOut(32, "Cambridge", 22)
    - Customer 32's trip: "Paradise" to "Cambridge", duration = 22 - 8 = 14.
  7. getAverageTime("Paradise", "Cambridge")
    - Only one trip: average = 14 / 1 = 14.0
  8. getAverageTime("Leyton", "Waterloo")
    - Two trips: (12 + 10) / 2 = 11.0

At each step, the check-in and check-out maps are updated efficiently, and the average is computed instantly.

Time and Space Complexity

Brute-force Approach:

  • Storing all trips individually and iterating to compute averages would make getAverageTime O(N), where N is the number of trips.
  • Space complexity would also be O(N) as every trip is stored.
Optimized Approach (as described above):
  • Each operation (checkIn, checkOut, getAverageTime) is O(1) due to hash map lookups/updates.
  • Space complexity is O(M + K), where M is the number of ongoing trips (customers currently checked in), and K is the number of unique station pairs.

This makes the system scalable and efficient even as the number of customers and trips increases.

Summary

The key to solving the "Design Underground System" problem efficiently is to use two hash maps: one for tracking ongoing journeys and one for aggregating travel data by station pair. This allows all operations to be performed in constant time, ensuring the system remains fast and scalable. The solution is elegant because it leverages simple data structures to provide robust and efficient functionality for a real-world scenario.

Code Implementation

class UndergroundSystem:
    def __init__(self):
        self.check_in = {}  # id -> (stationName, time)
        self.travel_data = {}  # (startStation, endStation) -> [totalTime, tripCount]

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

    def checkOut(self, id: int, stationName: str, t: int) -> None:
        startStation, startTime = self.check_in.pop(id)
        key = (startStation, stationName)
        if key not in self.travel_data:
            self.travel_data[key] = [0, 0]
        self.travel_data[key][0] += t - startTime
        self.travel_data[key][1] += 1

    def getAverageTime(self, startStation: str, endStation: str) -> float:
        totalTime, tripCount = self.travel_data[(startStation, endStation)]
        return totalTime / tripCount
      
class UndergroundSystem {
    unordered_map<int, pair<string, int>> check_in;
    unordered_map<string, pair<double, int>> travel_data;
public:
    UndergroundSystem() {}

    void checkIn(int id, string stationName, int t) {
        check_in[id] = {stationName, t};
    }

    void checkOut(int id, string stationName, int t) {
        auto [startStation, startTime] = check_in[id];
        check_in.erase(id);
        string key = startStation + "-" + stationName;
        auto &data = travel_data[key];
        data.first += (t - startTime);
        data.second += 1;
    }

    double getAverageTime(string startStation, string endStation) {
        string key = startStation + "-" + endStation;
        auto &data = travel_data[key];
        return data.first / data.second;
    }
};
      
class UndergroundSystem {
    private Map<Integer, Pair<String, Integer>> checkIn;
    private Map<String, double[]> travelData;

    public UndergroundSystem() {
        checkIn = new HashMap<>();
        travelData = new HashMap<>();
    }

    public void checkIn(int id, String stationName, int t) {
        checkIn.put(id, new Pair<>(stationName, t));
    }

    public void checkOut(int id, String stationName, int t) {
        Pair<String, Integer> entry = checkIn.remove(id);
        String key = entry.getKey() + "-" + stationName;
        double[] data = travelData.getOrDefault(key, new double[2]);
        data[0] += t - entry.getValue();
        data[1] += 1;
        travelData.put(key, data);
    }

    public double getAverageTime(String startStation, String endStation) {
        String key = startStation + "-" + endStation;
        double[] data = travelData.get(key);
        return data[0] / data[1];
    }

    // Helper Pair class (since Java doesn't have one by default)
    private static class Pair<K, V> {
        private K key;
        private V value;
        public Pair(K key, V value) {
            this.key = key;
            this.value = value;
        }
        public K getKey() { return key; }
        public V getValue() { return value; }
    }
}
      
var UndergroundSystem = function() {
    this.checkIn = new Map(); // id -> [stationName, time]
    this.travelData = new Map(); // "start-end" -> [totalTime, tripCount]
};

UndergroundSystem.prototype.checkIn = function(id, stationName, t) {
    this.checkIn.set(id, [stationName, t]);
};

UndergroundSystem.prototype.checkOut = function(id, stationName, t) {
    const [startStation, startTime] = this.checkIn.get(id);
    this.checkIn.delete(id);
    const key = startStation + '-' + stationName;
    if (!this.travelData.has(key)) {
        this.travelData.set(key, [0, 0]);
    }
    const data = this.travelData.get(key);
    data[0] += t - startTime;
    data[1] += 1;
};

UndergroundSystem.prototype.getAverageTime = function(startStation, endStation) {
    const key = startStation + '-' + endStation;
    const data = this.travelData.get(key);
    return data[0] / data[1];
};