1396. Design Underground System
At a Glance
- Topic: system-design-coding
- Pattern: Hash maps (trip state + rolling aggregates)
- Difficulty: Medium
- Companies: DoorDash, Uber, Lyft, Amazon, Bloomberg
- Frequency: Medium
- LeetCode: 1396
Problem (one-liner)
Simulate turnstiles: customers check in at a station and time, later check out at another station and time; answer rolling average travel duration between station pairs.
Recognition Cues
- Open trips keyed by id
- Aggregate statistics keyed by
(startStation -> endStation)
Diagram
At-a-glance flow (replace with problem-specific Mermaid as you refine this note). camelCase node IDs; no spaces in IDs.
Loading diagram…
Approaches
- Optimal — map
customerId→ active{entryStation, entryTime}; maprouteKey→{totalMinutes, tripCount}—O(1)per event amortized.
Optimal Solution
Go
package main
import "fmt"
// UndergroundSystem API:
// - Constructor()
// - void checkIn(id int, stationName string, timestamp int)
// - void checkOut(id int, stationName string, timestamp int)
// - float64 getAverageTime(startStation string, endStation string)
type activeTrip struct {
entryStation string
entryTimestamp int
}
type routeStats struct {
totalMinutes int
tripCount int
}
type UndergroundSystem struct {
openTrips map[int]activeTrip
stats map[string]routeStats
}
func Constructor() UndergroundSystem {
return UndergroundSystem{
openTrips: map[int]activeTrip{},
stats: map[string]routeStats{},
}
}
func (system *UndergroundSystem) CheckIn(customerId int, stationName string, timestamp int) {
system.openTrips[customerId] = activeTrip{
entryStation: stationName,
entryTimestamp: timestamp,
}
}
func (system *UndergroundSystem) CheckOut(customerId int, stationName string, timestamp int) {
trip := system.openTrips[customerId]
delete(system.openTrips, customerId)
routeKey := fmt.Sprintf("%s>%s", trip.entryStation, stationName)
aggregate := system.stats[routeKey]
duration := timestamp - trip.entryTimestamp
aggregate.totalMinutes += duration
aggregate.tripCount++
system.stats[routeKey] = aggregate
}
func (system *UndergroundSystem) GetAverageTime(startStation string, endStation string) float64 {
routeKey := fmt.Sprintf("%s>%s", startStation, endStation)
aggregate := system.stats[routeKey]
return float64(aggregate.totalMinutes) / float64(aggregate.tripCount)
}JavaScript
class UndergroundSystem {
constructor() {
this.openTrips = new Map();
this.stats = new Map();
}
checkIn(customerId, stationName, timestamp) {
this.openTrips.set(customerId, {
entryStation: stationName,
entryTimestamp: timestamp,
});
}
checkOut(customerId, stationName, timestamp) {
const trip = this.openTrips.get(customerId);
this.openTrips.delete(customerId);
const routeKey = `${trip.entryStation}>${stationName}`;
const duration = timestamp - trip.entryTimestamp;
const prior = this.stats.get(routeKey) || { totalMinutes: 0, tripCount: 0 };
prior.totalMinutes += duration;
prior.tripCount += 1;
this.stats.set(routeKey, prior);
}
getAverageTime(startStation, endStation) {
const routeKey = `${startStation}>${endStation}`;
const aggregate = this.stats.get(routeKey);
return aggregate.totalMinutes / aggregate.tripCount;
}
}Walkthrough
Customer 45 checks in Leyton at 3, checks out Waterloo at 15 → duration 12 aggregated under key Leyton>Waterloo.
Invariant: each checkout closes exactly one open trip; averages use integer sums divided by counts.
Edge Cases
- Same person never overlaps (guaranteed)
- Times monotonic per customer
Pitfalls
- String key composition collisions — use delimiter between station names
- Forgetting to delete open trip on checkout
Similar Problems
- 1166. Design File System — structured key namespaces (cousin design).
- 380. Insert Delete GetRandom O(1) — dual-structure orchestration.
Variants
- Percentiles instead of mean — store timings list or sketch.
- Route pruning after TTL.
Mind-Map Tags
#hash-map #streaming-stats #rolling-average #systems-design-lite
Last updated on
Spotted something unclear or wrong on this page?