You are commuting across a simplified map of San Francisco, represented as a 2D grid. Each cell on the grid is one of the following:
'S': Your home location (starting point)'D': Your office location (destination)'1' to k: A street segment reserved for exactly one transportation mode'X': An impassable roadblockYou're also given three arrays with length k:
modes: The name of each available transportation mode (e.g., ["bike", "bus", "walk", "scooter"])times: The time (in minutes) required to traverse a single block using each modecosts: The cost (in dollars) to traverse a single block using each mode'X')'S' to 'D' is not a valid commuteFor each mode i (0-indexed), the time and cost to traverse a single block are given by times[i] and costs[i], respectively. [Source: darkinterview.com]
The total travel time and total cost are calculated as the sum of the time and cost for each mode cell traversed along the path from 'S' to 'D'. Note that 'S' and 'D' are special cells (not mode cells) and contribute neither time nor cost.
Return the name of the transportation mode that yields the minimum total time from 'S' to 'D'.
"" if no valid route existsgrid = [
['S', '1', '1', '1', 'D'],
['2', '2', '2', '2', 'X']
]
modes = ["bike", "bus"]
times = [5, 3]
costs = [2, 1]
Grid layout: [Source: darkinterview.com]
Row 0: S 1 1 1 D
Row 1: 2 2 2 2 X
Mode 1 (bike): Path exists from S to D
Mode 2 (bus): Cannot reach D
"bike" # Only valid route with 15 minutes
1 <= grid.length, grid[0].length <= 100 (rows × columns)1 <= k <= 4 (number of transportation modes)modes.length == times.length == costs.length == k1 <= times[i], costs[i] <= 100'S' and one 'D'For each transportation mode: [Source: darkinterview.com]
'S''D'After all BFS runs, compare results and return the optimal mode.
k searches add only O(k) initialization overhead beyond the total number of visited cells.from collections import deque
from typing import List
def findOptimalCommute(grid: List[List[str]], modes: List[str],
times: List[int], costs: List[int]) -> str:
rows, cols = len(grid), len(grid[0])
# Find start and destination
start, dest = None, None
for r in range(rows):
for c in range(cols):
if grid[r][c] == 'S':
start = (r, c)
elif grid[r][c] == 'D':
dest = (r, c)
if not start or not dest:
return ""
best_time = float('inf')
best_cost = float('inf')
best_mode = ""
# Try each transportation mode
for mode_idx in range(len(modes)):
mode_digit = str(mode_idx + 1)
# BFS for this mode
queue = deque([(start[0], start[1], 0)]) # (row, col, distance)
visited = {start}
found =
queue found:
r, c, dist = queue.popleft()
(r, c) == dest:
total_time = dist * times[mode_idx]
total_cost = dist * costs[mode_idx]
(total_time < best_time
(total_time == best_time total_cost < best_cost)):
best_time = total_time
best_cost = total_cost
best_mode = modes[mode_idx]
found =
dr, dc [(, ), (, ), (, -), (-, )]:
nr, nc = r + dr, c + dc
( <= nr < rows <= nc < cols
(nr, nc) visited):
cell = grid[nr][nc]
can_enter_dest = cell == dist >
cell == mode_digit can_enter_dest:
visited.add((nr, nc))
new_dist = dist cell == dist +
queue.append((nr, nc, new_dist))
best_mode
We can also run one BFS from the start and explore all modes simultaneously by tracking which mode each queued path uses. This avoids maintaining a separate queue for every mode, although it does not improve the asymptotic complexity under this fixed-cell model.
'S', adding every adjacent digit cell to the queue. Do not enqueue 'D' directly because a valid route must use at least one mode cell(row, col, mode_used, distance)'D'(row, col, mode)'D', calculate time and cost, and update the best resultBy tracking (row, col, mode) in the visited set, we ensure each state is processed at most once. [Source: darkinterview.com]
Crucially: Each digit cell has a fixed mode (for example, a '1' cell can only be visited using mode 1). Therefore, each reachable digit cell is visited at most once. The destination may be reached once per mode because (D, mode) states are distinct.
Like the per-mode approach, this processes O(r × c + k) states in total. Its benefit is using one coordinated traversal rather than improving the worst-case asymptotic bound.
from collections import deque
from typing import List
def findOptimalCommuteOptimized(grid: List[List[str]], modes: List[str],
times: List[int], costs: List[int]) -> str:
rows, cols = len(grid), len(grid[0])
# Find start and destination
start, dest = None, None
for r in range(rows):
for c in range(cols):
if grid[r][c] == 'S':
start = (r, c)
elif grid[r][c] == 'D':
dest = (r, c)
if not start or not dest:
return ""
best_time = float('inf')
best_cost = float('inf')
best_mode = ""
# Single BFS: (row, col, mode_used, distance)
# mode_used is the digit of the transportation mode
queue = deque()
visited = set()
# Initialize: explore all neighbors of start
for dr, dc in [(0, 1), (1, 0), (0, -), (-, )]:
nr, nc = start[] + dr, start[] + dc
<= nr < rows <= nc < cols:
cell = grid[nr][nc]
cell.isdigit():
mode_digit = cell
visited.add((nr, nc, mode_digit))
queue.append((nr, nc, mode_digit, ))
queue:
r, c, mode_digit, dist = queue.popleft()
grid[r][c] == :
mode_idx = (mode_digit) -
total_time = dist * times[mode_idx]
total_cost = dist * costs[mode_idx]
(total_time < best_time
(total_time == best_time total_cost < best_cost)):
best_time = total_time
best_cost = total_cost
best_mode = modes[mode_idx]
dr, dc [(, ), (, ), (, -), (-, )]:
nr, nc = r + dr, c + dc
( <= nr < rows <= nc < cols
(nr, nc, mode_digit) visited):
cell = grid[nr][nc]
cell == mode_digit cell == :
visited.add((nr, nc, mode_digit))
new_dist = dist cell == dist +
queue.append((nr, nc, mode_digit, new_dist))
best_mode
Question: What if you can switch transportation modes mid-journey, but each switch incurs an additional switch_cost dollars and switch_time minutes? [Source: darkinterview.com]
a cell into a mode-b cell, where a != b, counts as one switch and adds switch_cost dollars and switch_time minutes'S' does not count as a switchswitch_time >= 0 and switch_cost >= 0'S' and 'D', together with its total time and total cost. Return None if no route existsUse Dijkstra's algorithm with a priority queue:
(total_time, total_cost, row, col, current_mode)(total_time, total_cost) (time first, then cost)(time, cost) for each (row, col, mode) statenext_mode_idx = int(cell) - 1)'D' to reconstruct the routeIn this grid model, each non-special cell has exactly one mode digit, so trying every possible mode at every cell is unnecessary. We explore the four neighboring cells, and each neighbor's digit determines the next mode. Keeping current_mode in the state makes the switch calculation explicit and supports variants in which a cell allows multiple modes.
A plain DFS with memoization is not a good fit because the grid is a cyclic, weighted graph rather than a DAG. Dijkstra's algorithm safely settles states in optimal (time, cost) order. If a generalized version allows several modes at one cell, memoizing by coordinate alone would also be insufficient; the current mode must be part of the state. [Source: darkinterview.com]
The coordinate route fully identifies the mode sequence because every traversed digit cell maps to exactly one mode.
V is the number of (cell, mode) states and E is the number of valid transitions between themimport heapq
from typing import List, Optional, Tuple
Coordinate = Tuple[int, int]
CommuteResult = Tuple[List[Coordinate], int, int]
def findOptimalCommuteWithSwitching(grid: List[List[str]], modes: List[str],
times: List[int], costs: List[int],
switch_time: int,
switch_cost: int) -> Optional[CommuteResult]:
rows, cols = len(grid), len(grid[0])
# Find start and destination
start, dest = None, None
for r in range(rows):
for c in range(cols):
if grid[r][c] == 'S':
start = (r, c)
elif grid[r][c] == 'D':
dest = (r, c)
if not start or not dest:
return None
# Use mode -1 only for the initial state at S.
start_state = (start[0], start[1], -1)
# Priority queue: (total_time, total_cost, row, col, mode_idx)
pq = [(, , start[], start[], -)]
best = {start_state: (, )}
parent = {}
pq:
curr_time, curr_cost, r, c, mode_idx = heapq.heappop(pq)
state = (r, c, mode_idx)
best.get(state) != (curr_time, curr_cost):
(r, c) == dest:
path = []
current = state
:
path.append((current[], current[]))
current == start_state:
current = parent[current]
path.reverse()
path, curr_time, curr_cost
dr, dc [(, ), (, ), (, -), (-, )]:
nr, nc = r + dr, c + dc
<= nr < rows <= nc < cols:
cell = grid[nr][nc]
cell == :
mode_idx == -:
next_mode_idx = mode_idx
new_time, new_cost = curr_time, curr_cost
cell.isdigit():
next_mode_idx = (cell) -
new_time = curr_time + times[next_mode_idx]
new_cost = curr_cost + costs[next_mode_idx]
mode_idx != - next_mode_idx != mode_idx:
new_time += switch_time
new_cost += switch_cost
:
next_state = (nr, nc, next_mode_idx)
candidate = (new_time, new_cost)
candidate < best.get(next_state, ((), ())):
best[next_state] = candidate
parent[next_state] = state
heapq.heappush(
pq,
(new_time, new_cost, nr, nc, next_mode_idx)
)
Question: What if you can switch modes at most max_switches times?
Return the same route, total time, and total cost as in Follow-Up 1, subject to the switch limit. [Source: darkinterview.com]
Modify the Dijkstra's algorithm to include the number of switches in the state:
(total_time, total_cost, row, col, current_mode, switches_used)best[row][col][mode][switches_used]switches_used < max_switches