DEV Community

Cover image for Solution: Design Underground System
seanpgallivan
seanpgallivan

Posted on

Solution: Design Underground System

This is part of a series of Leetcode solution explanations (index). If you liked this solution or found it useful, please like this post and/or upvote my solution post on Leetcode's forums.


Leetcode Problem #1396 (Medium): Design Underground System


Description:


(Jump to: Solution Idea || Code: JavaScript | Python | Java | C++)

Implement the UndergroundSystem class:

  • void checkIn(int id, string stationName, int t)
    • A customer with a card id equal to id, gets in the station stationName at time t.
    • A customer can only be checked into one place at a time.
  • void checkOut(int id, string stationName, int t)
    • A customer with a card id equal to id, gets out from the station stationName at time t.
  • double getAverageTime(string startStation, string endStation)
    • Returns the average time to travel between the startStation and the endStation.
    • The average time is computed from all the previous traveling from startStation to endStation that happened directly.
    • Call to getAverageTime is always valid.

You can assume all calls to checkIn and checkOut methods are consistent. If a customer gets in at time t1 at some station, they get out at time t2 with t2 > t1. All events happen in chronological order.


Examples:

Example 1:
Input: example
Example 1:
Input ["UndergroundSystem", "checkIn", "checkIn", "checkIn", "checkOut", "checkOut", "checkOut", "getAverageTime", "getAverageTime", "checkIn", "getAverageTime", "checkOut", "getAverageTime"]
[[], [45,"Leyton",3], [32,"Paradise",8], [27,"Leyton",10], [45,"Waterloo",15], [27,"Waterloo",20], [32,"Cambridge",22], ["Paradise","Cambridge"], ["Leyton","Waterloo"], [10,"Leyton",24], ["Leyton","Waterloo"], [10,"Waterloo",38], ["Leyton","Waterloo"]]
Output [null, null, null, null, null, null, null, 14.00000, 11.00000, null, 11.00000, null, 12.00000]
Explanation UndergroundSystem undergroundSystem = new UndergroundSystem();
undergroundSystem.checkIn(45, "Leyton", 3);
undergroundSystem.checkIn(32, "Paradise", 8);
undergroundSystem.checkIn(27, "Leyton", 10);
undergroundSystem.checkOut(45, "Waterloo", 15);
undergroundSystem.checkOut(27, "Waterloo", 20);
undergroundSystem.checkOut(32, "Cambridge", 22);
undergroundSystem.getAverageTime("Paradise", "Cambridge"); // return 14.00000. There was only one travel from "Paradise" (at time 8) to "Cambridge" (at time 22)
undergroundSystem.getAverageTime("Leyton", "Waterloo"); // return 11.00000. There were two travels from "Leyton" to "Waterloo", a customer with id=45 from time=3 to time=15 and a customer with id=27 from time=10 to time=20. So the average time is ( (15-3) + (20-10) ) / 2 = 11.00000
undergroundSystem.checkIn(10, "Leyton", 24);
undergroundSystem.getAverageTime("Leyton", "Waterloo"); // return 11.00000
undergroundSystem.checkOut(10, "Waterloo", 38);
undergroundSystem.getAverageTime("Leyton", "Waterloo"); // return 12.00000
Example 2:
Input ["UndergroundSystem", "checkIn", "checkOut", "getAverageTime", "checkIn", "checkOut", "getAverageTime", "checkIn", "checkOut", "getAverageTime"]
[[], [10,"Leyton",3], [10,"Paradise",8], ["Leyton","Paradise"], [5,"Leyton",10], [5,"Paradise",16], ["Leyton","Paradise"], [2,"Leyton",21], [2,"Paradise",30], ["Leyton","Paradise"]]
Output [null, null, null, 5.00000, null, null, 5.50000, null, null, 6.66667]
Explanation UndergroundSystem undergroundSystem = new UndergroundSystem();
undergroundSystem.checkIn(10, "Leyton", 3);
undergroundSystem.checkOut(10, "Paradise", 8);
undergroundSystem.getAverageTime("Leyton", "Paradise"); // return 5.00000
undergroundSystem.checkIn(5, "Leyton", 10);
undergroundSystem.checkOut(5, "Paradise", 16);
undergroundSystem.getAverageTime("Leyton", "Paradise"); // return 5.50000
undergroundSystem.checkIn(2, "Leyton", 21);
undergroundSystem.checkOut(2, "Paradise", 30);
undergroundSystem.getAverageTime("Leyton", "Paradise"); // return 6.66667

Constraints:

  • There will be at most 20000 operations.
  • 1 <= id, t <= 10^6
  • All strings consist of uppercase and lowercase English letters, and digits.
  • 1 <= stationName.length <= 10
  • Answers within 10^-5 of the actual value will be accepted as correct.

Idea:


(Jump to: Problem Description || Code: JavaScript | Python | Java | C++)

The first thing we should realize is that since checkins and checkouts are separated, we'll need some type of data structure in which to store checkin information until we find the matching checkout information.

The next important realization is that since we only ultimately care about route times, we don't really need to store checkout info at all. As long as we store the checkin info until we get to the checkout info, we can actually just then store the trip info separately by route and get rid of the checkin and checkout information. This will help keep the space needed to a minimum.

As we'll want to look up checkin and route info by id and route name, we should use Map structures for both (checkins & routes). For the route information, we'll only need to keep track of the number of trips and the total duration, so that we can calculate the average as needed. We can also use a concatenated name for the key in the route map in order to store the trip as a whole, rather than having to keep track of both ends separately.


Implementation:

For better efficiency, Javascript can use a Uint32Array for route, Python can use a tuple for checkins, and Java and C++ can use Pairs for checkins and Java can use Pairs instead of concatenating the station names.


Javascript Code:


(Jump to: Problem Description || Solution Idea)

class UndergroundSystem {
    constructor() {
        this.checkins = new Map()
        this.routes = new Map()
    }
    checkIn(id, stationName, t) {
        this.checkins.set(id, [stationName, t])
    };
    checkOut(id, stationName, t) {
        let [stn, start] = this.checkins.get(id),
            route = stn + "," + stationName
        this.checkins.delete(id)
        if (!this.routes.has(route))
            this.routes.set(route, new Uint32Array(2))
        let trip = this.routes.get(route)
        trip[0]++, trip[1] += t - start
    };
    getAverageTime(startStation, endStation) {
        let [count, sum] = this.routes.get(startStation + "," + endStation)
        return sum / count
    };
};
Enter fullscreen mode Exit fullscreen mode

Python Code:


(Jump to: Problem Description || Solution Idea)

class UndergroundSystem:
    def __init__(self):
        self.checkins = defaultdict()
        self.routes = defaultdict()
    def checkIn(self, id: int, stationName: str, t: int) -> None:
        self.checkins[id] = (stationName, t)
    def checkOut(self, id: int, stationName: str, t: int) -> None:
        stn, start = self.checkins[id]
        del self.checkins[id]
        route = stn + "," + stationName
        if route not in self.routes: self.routes[route] = [0,0]
        trip = self.routes[route]
        trip[0] += 1
        trip[1] += t - start
    def getAverageTime(self, startStation: str, endStation: str) -> float:
        count, rsum = self.routes[startStation + "," + endStation]
        return rsum / count
Enter fullscreen mode Exit fullscreen mode

Java Code:


(Jump to: Problem Description || Solution Idea)

class UndergroundSystem {
    Map<Integer, Pair<String, Integer>> checkins = new HashMap<>();
    Map<Pair<String, String>, int[]> routes = new HashMap<>();
    public void checkIn(int id, String stationName, int t) {
        checkins.put(id, new Pair(stationName, t));
    }
    public void checkOut(int id, String stationName, int t) {
        Pair<String, Integer> cIn = checkins.get(id);
        checkins.remove(id);
        Pair<String, String> route = new Pair(cIn.getKey(), stationName);
        int[] trip = routes.getOrDefault(route, new int[2]);
        trip[0]++;
        trip[1] += t - cIn.getValue();
        routes.put(route, trip);
    }
    public double getAverageTime(String startStation, String endStation) {
        int[] trip = routes.get(new Pair(startStation, endStation));
        return (double)trip[1] / trip[0];
    }
}
Enter fullscreen mode Exit fullscreen mode

C++ Code:


(Jump to: Problem Description || Solution Idea)

class UndergroundSystem {
public:
    unordered_map<int, pair<string, int>> checkins;
    unordered_map<string, pair<int, int>> routes;
    void checkIn(int id, string stationName, int t) {
        checkins[id] = {stationName, t};
    }
    void checkOut(int id, string stationName, int t) {
        auto [stn, start] = checkins[id];
        checkins.erase(id);
        string route = stn + "," + stationName;
        routes[route].first++, routes[route].second += t - start;
    }
    double getAverageTime(string startStation, string endStation) {
        auto& [count, sum] = routes[startStation + "," + endStation];
        return (double)sum / count;
    }
};
Enter fullscreen mode Exit fullscreen mode

Top comments (0)