TL;DR
Traverse the tree outward from city 0 (DFS or BFS); count edges that point away from 0 — those must be reversed — O(n) time, O(n) space.
Approach 1 — Traverse from 0, count outward edges (DFS)
The graph is a tree, so a single traversal from city 0 is optimal; there is no brute force worth writing. The work is in modeling direction.
Ignore direction to walk the tree, but keep each road’s true orientation. Root the tree at city 0. For every city to reach 0, every edge must point toward 0 (child→parent). As you DFS outward from 0 to a child, if the actual road runs parent→child (away from 0), it is oriented wrong and needs one reversal. Store neighbors as (neighbor, is_original), where is_original = True for the real direction a→b and False for the added reverse direction.
def minReorder(n: int, connections: list[list[int]]) -> int:
adj = [[] for _ in range(n)]
for a, b in connections:
adj[a].append((b, 1)) # real road a->b : crossing a->b is "away from 0"
adj[b].append((a, 0)) # phantom reverse b->a : free to traverse
visited = [False] * n
reversals = 0
def dfs(city: int) -> None:
nonlocal reversals
visited[city] = True
for nxt, is_original in adj[city]:
if not visited[nxt]:
reversals += is_original # crossing an outward road costs 1
dfs(nxt)
dfs(0)
return reversals
Walkthrough (connections = [[0,1],[1,3],[2,3],[4,0],[4,5]]): DFS from 0. 0→1 uses real road 0→1 (away from 0) → +1. 1→3 uses real 1→3 → +1. 3→2 uses phantom reverse of 2→3 → +0. Back out; 0→4 uses phantom reverse of 4→0 → +0. 4→5 uses real 4→5 → +1. Total 3.
Each edge below is drawn in its real road direction. The three labeled reverse point away from 0 and must be flipped:
flowchart TD
N0((0))
N1((1))
N2((2))
N3((3))
N4((4))
N5((5))
N0 -->|reverse| N1
N1 -->|reverse| N3
N2 --> N3
N4 --> N0
N4 -->|reverse| N5
Complexity: each node and each edge is visited once → O(n) time, O(n) space (adjacency list + recursion stack).
Approach 2 — BFS variant
Identical accounting, but expand from city 0 level by level with a queue. BFS is preferred here because n can be 50,000 and the tree may be a long path, so recursive DFS risks exceeding Python’s recursion limit. Use DFS when depth is safely bounded and you want shorter code.
from collections import deque
def minReorder(n: int, connections: list[list[int]]) -> int:
adj = [[] for _ in range(n)]
for a, b in connections:
adj[a].append((b, 1))
adj[b].append((a, 0))
visited = [False] * n
visited[0] = True
reversals = 0
queue = deque([0])
while queue:
city = queue.popleft()
for nxt, is_original in adj[city]:
if not visited[nxt]:
visited[nxt] = True
reversals += is_original
queue.append(nxt)
return reversals
Complexity: O(n) time, O(n) space.
Common pitfalls
- Only adding the directed edge. You must store both orientations (real with a flag
1, phantom reverse with 0) so you can walk the undirected tree while still knowing each road’s true direction.
- Counting inward edges. The cost is for edges pointing away from 0 (parent→child). Add
is_original, not its complement.
- Forgetting the visited guard. Even though it’s a tree, the phantom reverse edges make the adjacency list undirected — without
visited you’d bounce back to the parent forever.
- Recursion depth on a path-shaped tree. With n up to 5·10⁴, a linear chain overflows recursive DFS; use BFS or raise the recursion limit.
Pattern takeaway
For a directed tree (“all roads lead to 0”), root at the target and do one traversal, storing each edge with a direction flag so you can walk the underlying undirected tree while charging a cost for edges oriented the wrong way. The reusable trick: add both the real edge (weight/flag 1) and its reverse (flag 0) to the adjacency list, then DFS/BFS once — trees need no cycle handling beyond a visited mark.