Before attempting this problem, you should be comfortable with:
This is a shortest path problem where we want to find the minimum number of buses needed to travel from a source stop to a target stop. We can model this as a graph problem where stops are nodes, and we use BFS to find the shortest path in terms of bus transfers.
The key insight is that when we board a bus, we can reach all stops on that route. So from any stop, we can transition to all other stops on any bus that serves that stop. We count bus transfers (not individual stops) to measure distance.
source equals target, return 0 (no bus needed).-1.class Solution:
def numBusesToDestination(self, routes: List[List[int]], source: int, target: int) -> int:
if source == target:
return 0
n = len(routes)
stops = defaultdict(list)
for bus in range(n):
for stop in routes[bus]:
stops[stop].append(bus)
seen_bus = set()
seen_stop = set([source])
res = 0
q = deque([source])
while q:
for _ in range(len(q)):
stop = q.popleft()
if stop == target:
return res
for bus in stops[stop]:
if bus in seen_bus:
continue
seen_bus.add(bus)
for nxtStop in routes[bus]:
if nxtStop in seen_stop:
continue
seen_stop.add(nxtStop)
q.append(nxtStop)
res += 1
return -1Where is the number of routes and is the maximum number of stops per bus route.
Instead of treating stops as nodes, we can treat bus routes as nodes. Two routes are connected if they share at least one common stop (you can transfer between them). This transforms the problem into finding the shortest path between any route containing the source stop and any route containing the target stop.
This approach can be more efficient when there are many stops but fewer routes, as we traverse through routes rather than individual stops.
source equals target, return 0.-1.class Solution:
def numBusesToDestination(self, routes: List[List[int]], source: int, target: int) -> int:
if source == target:
return 0
n = len(routes)
adjList = [[] for _ in range(n)]
stopToRoutes = defaultdict(list)
for bus, route in enumerate(routes):
for stop in route:
stopToRoutes[stop].append(bus)
if target not in stopToRoutes or source not in stopToRoutes:
return -1
hasEdge = [[False] * n for _ in range(n)]
for buses in stopToRoutes.values():
for i in range(len(buses)):
for j in range(i + 1, len(buses)):
if hasEdge[buses[i]][buses[j]]:
continue
hasEdge[buses[i]][buses[j]] = True
hasEdge[buses[j]][buses[i]] = True
adjList[buses[i]].append(buses[j])
adjList[buses[j]].append(buses[i])
q = deque([node for node in stopToRoutes[source]])
res = 1
while q:
for _ in range(len(q)):
node = q.popleft()
if node in stopToRoutes[target]:
return res
while adjList[node]:
nxtBus = adjList[node].pop()
if adjList[nxtBus]:
q.append(nxtBus)
res += 1
return -1Where is the number of routes and is the maximum number of stops per bus route.
If the source and target are the same stop, no bus is needed. Forgetting this check causes unnecessary computation or incorrect results when the BFS never finds the target in its traversal.
# Wrong: missing early return
def numBusesToDestination(self, routes, source, target):
# starts BFS without checking source == targetThe problem asks for minimum number of buses, not minimum stops visited. A common mistake is incrementing the counter for each stop rather than each bus transfer.
# Wrong: incrementing for each stop
for nxtStop in routes[bus]:
q.append(nxtStop)
res += 1 # Should be outside the inner loopOnly tracking visited stops leads to revisiting the same bus multiple times, causing TLE. Only tracking visited buses may revisit stops unnecessarily. Both sets are needed for efficiency.
# Wrong: only tracking stops
seen_stop = set()
# Missing: seen_bus = set()Incrementing the bus count at the wrong position (before checking if target is reached, or inside the wrong loop) leads to off-by-one errors in the result.
When using routes as nodes, failing to deduplicate edges between routes that share multiple stops creates redundant work and can cause memory issues with large inputs.