Before attempting this problem, you should be comfortable with:
We need to find shortest paths from node 0 to all other nodes, but with a twist: the path must alternate between red and blue edges. The key insight is that reaching a node via a red edge is different from reaching it via a blue edge, because it affects which color we can use next. So we track states as (node, last_edge_color) pairs. BFS naturally finds shortest paths in unweighted graphs, and since we want the first time we reach each node, we record the distance on first visit.
-1 for all nodes.BFS from node 0 with no previous edge color (allowing either color first).(node, length, edgeColor):length as its answer.(node, color) to avoid cycles.class Solution:
def shortestAlternatingPaths(self, n: int, redEdges: list[list[int]], blueEdges: list[list[int]]) -> list[int]:
red, blue = defaultdict(list), defaultdict(list)
for src, dst in redEdges:
red[src].append(dst)
for src, dst in blueEdges:
blue[src].append(dst)
answer = [-1 for _ in range(n)]
q = deque()
q.append((0, 0, None)) # [node, length, prev_edge_color]
visit = set()
visit.add((0, None))
while q:
node, length, edgeColor = q.popleft()
if answer[node] == -1:
answer[node] = length
if edgeColor != "RED":
for nei in red[node]:
if (nei, "RED") not in visit:
visit.add((nei, "RED"))
q.append((nei, length + 1, "RED"))
if edgeColor != "BLUE":
for nei in blue[node]:
if (nei, "BLUE") not in visit:
visit.add((nei, "BLUE"))
q.append((nei, length + 1, "BLUE"))
return answerWhere is the number of vertices and is the number of edges.
This approach uses a cleaner state representation. Instead of tracking the edge color explicitly, we use 0 for red and 1 for blue, and toggle between them using XOR (color ^ 1). We maintain a 2D distance array where dist[node][color] stores the shortest distance to reach node when the last edge used was color. Starting from node 0 with both colors as valid starting points, we relax edges only when we find a shorter path.
0) and blue (index 1) edges.dist[0][0] = dist[0][1] = 0.BFS with two initial states: (0, 0) and (0, 1) representing starting with red or blue.(node, color):dist[neighbor][opposite_color] > dist[node][color] + 1, update it and enqueue.dist[node][0] and dist[node][1].-1 for nodes where both distances remain infinity.class Solution:
def shortestAlternatingPaths(self, n: int, redEdges: list[list[int]], blueEdges: list[list[int]]) -> list[int]:
def buildGraph(edges):
adj = [[] for _ in range(n)]
for u, v in edges:
adj[u].append(v)
return adj
red, blue = buildGraph(redEdges), buildGraph(blueEdges)
adj = [red, blue]
INF = float("inf")
dist = [[INF] * 2 for _ in range(n)]
dist[0][0] = dist[0][1] = 0
q = deque([(0, 0), (0, 1)])
while q:
node, color = q.popleft()
for nei in adj[color][node]:
if dist[nei][color ^ 1] > dist[node][color] + 1:
dist[nei][color ^ 1] = dist[node][color] + 1
q.append((nei, color ^ 1))
answer = [0] + [-1] * (n - 1)
for i in range(1, n):
answer[i] = min(dist[i][0], dist[i][1])
if answer[i] == INF:
answer[i] = -1
return answerWhere is the number of vertices and is the number of edges.
While BFS is typically preferred for shortest path problems, DFS can also work here because we are tracking distances and only updating when we find a shorter path. The recursion naturally handles the alternating color constraint. We start DFS from node 0 twice: once beginning with red edges and once with blue. Whenever we find a shorter path to a node with a particular ending color, we update the distance and continue exploring.
dist[0][0] = dist[0][1] = 0.DFS starting from node 0 with color 0 (red), then again with color 1 (blue).DFS for state (node, color):dist[neighbor][opposite_color] > dist[node][color] + 1:DFS on the neighbor with the opposite color.-1 for unreachable nodes.class Solution:
def shortestAlternatingPaths(self, n: int, redEdges: list[list[int]], blueEdges: list[list[int]]) -> list[int]:
def buildGraph(edges):
adj = [[] for _ in range(n)]
for u, v in edges:
adj[u].append(v)
return adj
red, blue = buildGraph(redEdges), buildGraph(blueEdges)
adj = [red, blue]
INF = float("inf")
dist = [[INF] * 2 for _ in range(n)]
dist[0][0] = dist[0][1] = 0
def dfs(node, color):
for nei in adj[color][node]:
if dist[nei][color ^ 1] > dist[node][color] + 1:
dist[nei][color ^ 1] = dist[node][color] + 1
dfs(nei, color ^ 1)
dfs(0, 0)
dfs(0, 1)
answer = [0] + [-1] * (n - 1)
for i in range(1, n):
answer[i] = min(dist[i][0], dist[i][1])
if answer[i] == INF:
answer[i] = -1
return answerWhere is the number of vertices and is the number of edges.
A common mistake is marking nodes as visited based solely on the node ID, ignoring the color of the edge used to reach it. Since reaching node X via a red edge allows different future moves than reaching it via a blue edge, you must track visited states as (node, last_edge_color) pairs. Without this, you may prematurely block valid paths that use a different edge color.
Since there is no edge leading into node 0, both red and blue edges are valid first moves. Failing to initialize the BFS with both color options (or using a sentinel value like None or -1 for "no previous color") means you might miss the shortest path if it requires starting with a specific color.
In BFS for shortest paths, the first time you reach a node is guaranteed to be via the shortest path. A pitfall is updating the answer every time a node is encountered rather than only on the first visit. This can lead to incorrect results if you overwrite an earlier (shorter) distance with a later (longer) one when the node is reached through a different color sequence.