Facebook Pixel

1466. Reorder Routes to Make All Paths Lead to the City Zero

Problem Description

You are given n cities numbered from 0 to n - 1 connected by n - 1 one-way roads, forming a tree structure where there's exactly one path between any two cities. The roads are represented by a list called connections, where each connections[i] = [ai, bi] indicates a directed road from city ai to city bi.

There's going to be a big event in the capital city (city 0), and people from all other cities need to be able to travel to city 0. However, due to the current direction of the roads, not all cities can reach the capital.

Your task is to find the minimum number of roads whose directions need to be reversed so that every city can reach city 0.

For example, if you have roads 0β†’1, 0β†’2, and 3β†’2, cities 1 and 2 can't reach city 0 because the roads point away from it. You'd need to reverse the directions of roads 0β†’1 and 0β†’2 to make them 1β†’0 and 2β†’0. City 3 can already reach city 0 through city 2 (following the path 3β†’2 then 2β†’0 after reversal), so you need to change 2 road directions in total.

The problem guarantees that the cities and roads form a valid tree structure, meaning there's always a solution where every city can reach city 0 after some reorientations.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: The problem explicitly involves cities (nodes) connected by roads (edges), forming a network structure. We have n cities and n-1 roads connecting them.

Is it a tree?

  • Yes: The problem states there are n cities and n-1 roads with exactly one way to travel between any two cities. This is the definition of a tree - a connected graph with no cycles. The problem even explicitly mentions "this network forms a tree."

DFS

  • Following the flowchart, when we have a tree structure, the natural algorithm choice is DFS (Depth-First Search).

Conclusion: The flowchart suggests using DFS for this problem. This makes perfect sense because:

  1. We need to traverse a tree structure starting from city 0
  2. We need to visit all nodes and check the direction of edges along the way
  3. DFS allows us to systematically explore all paths from the root (city 0) to all other cities
  4. During the DFS traversal, we can count how many edges need to be reversed (those pointing away from city 0 in our traversal direction)

The DFS approach perfectly matches the solution where we start from city 0 and traverse to all other cities, counting the edges that need reversal along the way.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is to think about this problem from a different perspective. Instead of trying to make all cities reach city 0, we can flip our thinking: start from city 0 and try to reach all other cities. This mental flip simplifies the problem significantly.

Since we have a tree structure (n cities with n-1 roads), we know that there's exactly one path between any two cities. This means if we can traverse from city 0 to all other cities, we just need to count how many roads are pointing in the "wrong" direction during our traversal.

Consider what happens when we perform DFS from city 0:

  • If we encounter a road from city a to city b in our original connections, and we're traversing from a to b, the road is already pointing the wrong way (away from city 0). We need to reverse it.
  • If we're traversing from b to a, the road is pointing the right way (toward city 0). No reversal needed.

To implement this, we can build an undirected graph representation but mark each edge with whether it needs reversal:

  • For each original directed edge [a, b], we add both (aβ†’b, cost=1) and (bβ†’a, cost=0) to our graph
  • The cost of 1 means we need to reverse the original edge if we traverse it in that direction
  • The cost of 0 means the original edge already points in the right direction

When we DFS from city 0, we sum up all the costs of edges we traverse. This gives us the minimum number of reversals needed because:

  1. We visit every city exactly once (tree property)
  2. Each edge we traverse either needs reversal (cost=1) or doesn't (cost=0)
  3. The sum of costs equals the total number of reversals required

This approach elegantly transforms a seemingly complex reorientation problem into a simple graph traversal with edge weights representing reversal costs.

Learn more about Depth-First Search, Breadth-First Search and Graph patterns.

Solution Approach

The implementation uses DFS with an adjacency list representation of the graph. Let's walk through the solution step by step:

1. Graph Construction:

g = [[] for _ in range(n)]
for a, b in connections:
    g[a].append((b, 1))  # Original edge: a→b needs reversal (cost=1)
    g[b].append((a, 0))  # Reverse edge: b→a doesn't need reversal (cost=0)

We build an adjacency list g where each city has a list of (neighbor, cost) tuples:

  • For each original directed edge [a, b], we add it to the graph twice
  • g[a].append((b, 1)): If we traverse from a to b, we're going against the original direction (away from city 0), so we need to reverse this edge (cost=1)
  • g[b].append((a, 0)): If we traverse from b to a, we're following the reverse of the original direction (toward city 0), so no reversal needed (cost=0)

2. DFS Traversal:

def dfs(a: int, fa: int) -> int:
    return sum(c + dfs(b, a) for b, c in g[a] if b != fa)

The DFS function takes two parameters:

  • a: Current city we're visiting
  • fa: Parent city (where we came from) to avoid revisiting

The function recursively:

  • Iterates through all neighbors (b, c) of current city a
  • Skips the parent city fa to avoid going backward in the tree
  • For each unvisited neighbor b, adds the edge cost c (0 or 1) to the result
  • Recursively calls DFS on neighbor b with a as its parent
  • Returns the sum of all reversal costs in the subtree rooted at a

3. Starting the DFS:

return dfs(0, -1)

We start DFS from city 0 with parent -1 (indicating no parent for the root). The function returns the total number of edges that need to be reversed.

Time Complexity: O(n) - We visit each city once and process each edge twice (once from each direction).

Space Complexity: O(n) - For the adjacency list and recursion stack depth (which can be up to n in a skewed tree).

The elegance of this solution lies in encoding the reversal information directly into the graph structure, allowing a simple DFS traversal to calculate the answer by just summing up edge costs.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example with 4 cities and the following connections:

  • connections = [[0,1], [0,2], [3,2]]

This creates a tree where:

  • City 0 connects to city 1 (road: 0β†’1)
  • City 0 connects to city 2 (road: 0β†’2)
  • City 3 connects to city 2 (road: 3β†’2)

Step 1: Build the Graph

For each directed edge, we add two entries to our adjacency list:

  • For edge [0,1]: Add (1, cost=1) to g[0] and (0, cost=0) to g[1]
  • For edge [0,2]: Add (2, cost=1) to g[0] and (0, cost=0) to g[2]
  • For edge [3,2]: Add (2, cost=1) to g[3] and (3, cost=0) to g[2]

Our adjacency list becomes:

g[0] = [(1, 1), (2, 1)]  // From 0, we can go to 1 and 2 (both need reversal)
g[1] = [(0, 0)]          // From 1, we can go to 0 (no reversal needed)
g[2] = [(0, 0), (3, 0)]  // From 2, we can go to 0 and 3 (neither needs reversal)
g[3] = [(2, 1)]          // From 3, we can go to 2 (needs reversal)

Step 2: DFS Traversal from City 0

Starting dfs(0, -1):

  • Current city: 0, parent: -1
  • Neighbors: [(1, 1), (2, 1)]
  • Process neighbor 1:
    • Cost = 1 (edge 0β†’1 needs reversal)
    • Call dfs(1, 0):
      • Current city: 1, parent: 0
      • Neighbors: [(0, 0)]
      • Skip 0 (it's the parent)
      • Return 0
    • Subtotal from branch 1: 1 + 0 = 1
  • Process neighbor 2:
    • Cost = 1 (edge 0β†’2 needs reversal)
    • Call dfs(2, 0):
      • Current city: 2, parent: 0
      • Neighbors: [(0, 0), (3, 0)]
      • Skip 0 (it's the parent)
      • Process neighbor 3:
        • Cost = 0 (edge 3β†’2 doesn't need reversal when traversing 2β†’3)
        • Call dfs(3, 2):
          • Current city: 3, parent: 2
          • Neighbors: [(2, 1)]
          • Skip 2 (it's the parent)
          • Return 0
        • Subtotal from branch 3: 0 + 0 = 0
      • Return 0
    • Subtotal from branch 2: 1 + 0 = 1
  • Total: 1 + 1 = 2

Result: 2 roads need to be reversed (0β†’1 becomes 1β†’0, and 0β†’2 becomes 2β†’0)

This matches our intuition: cities 1 and 2 cannot reach city 0 because the roads point away from it. After reversing these two roads, all cities can reach the capital.

Solution Implementation

1class Solution:
2    def minReorder(self, n: int, connections: List[List[int]]) -> int:
3        """
4        Count minimum number of edges to reverse so all cities can reach city 0.
5      
6        Args:
7            n: Number of cities (nodes)
8            connections: List of directed edges [from_city, to_city]
9          
10        Returns:
11            Minimum number of edges that need to be reversed
12        """
13      
14        def count_reversals(current_city: int, parent_city: int) -> int:
15            """
16            DFS to count edges that need reversing from current_city subtree.
17          
18            Args:
19                current_city: Current city being visited
20                parent_city: Parent city in DFS traversal (to avoid revisiting)
21              
22            Returns:
23                Number of edges to reverse in this subtree
24            """
25            total_reversals = 0
26          
27            # Visit all neighbors of current city
28            for neighbor_city, needs_reversal in adjacency_list[current_city]:
29                # Skip the parent to avoid going back in DFS
30                if neighbor_city != parent_city:
31                    # Add reversal cost (1 if edge needs reversing, 0 otherwise)
32                    # Then recursively count reversals in the subtree
33                    total_reversals += needs_reversal + count_reversals(neighbor_city, current_city)
34          
35            return total_reversals
36      
37        # Build adjacency list with bidirectional edges
38        # Each edge stores (destination_city, reversal_cost)
39        adjacency_list = [[] for _ in range(n)]
40      
41        for from_city, to_city in connections:
42            # Original edge: from_city -> to_city (costs 1 to reverse)
43            adjacency_list[from_city].append((to_city, 1))
44            # Reverse edge: to_city -> from_city (costs 0, already points to root direction)
45            adjacency_list[to_city].append((from_city, 0))
46      
47        # Start DFS from city 0 with no parent (-1)
48        return count_reversals(0, -1)
49
1class Solution {
2    // Adjacency list where each node stores pairs of [neighbor, edgeDirection]
3    // edgeDirection: 1 means original direction (needs reversal), 0 means already reversed
4    private List<int[]>[] adjacencyList;
5
6    public int minReorder(int n, int[][] connections) {
7        // Initialize adjacency list for n nodes
8        adjacencyList = new List[n];
9        Arrays.setAll(adjacencyList, index -> new ArrayList<>());
10      
11        // Build bidirectional graph with edge direction markers
12        for (int[] connection : connections) {
13            int fromNode = connection[0];
14            int toNode = connection[1];
15          
16            // Original edge: fromNode -> toNode (marked as 1, needs reversal to reach 0)
17            adjacencyList[fromNode].add(new int[] {toNode, 1});
18          
19            // Reverse edge: toNode -> fromNode (marked as 0, already points toward 0)
20            adjacencyList[toNode].add(new int[] {fromNode, 0});
21        }
22      
23        // Start DFS from node 0 with no parent (-1)
24        return dfs(0, -1);
25    }
26
27    private int dfs(int currentNode, int parentNode) {
28        int reversalCount = 0;
29      
30        // Explore all neighbors of current node
31        for (int[] edge : adjacencyList[currentNode]) {
32            int neighborNode = edge[0];
33            int needsReversal = edge[1];
34          
35            // Skip the parent node to avoid revisiting
36            if (neighborNode != parentNode) {
37                // Add reversal cost (0 or 1) and continue DFS on neighbor
38                reversalCount += needsReversal + dfs(neighborNode, currentNode);
39            }
40        }
41      
42        return reversalCount;
43    }
44}
45
1class Solution {
2public:
3    int minReorder(int n, vector<vector<int>>& connections) {
4        // Build adjacency list with bidirectional edges
5        // Each edge stores: (neighbor_node, needs_reversal_flag)
6        // needs_reversal_flag = 1 if edge points away from node 0, 0 otherwise
7        vector<vector<pair<int, int>>> adjacencyList(n);
8      
9        for (const auto& connection : connections) {
10            int fromNode = connection[0];
11            int toNode = connection[1];
12          
13            // Original edge: fromNode -> toNode (needs reversal if used)
14            adjacencyList[fromNode].emplace_back(toNode, 1);
15          
16            // Reverse edge: toNode -> fromNode (doesn't need reversal)
17            adjacencyList[toNode].emplace_back(fromNode, 0);
18        }
19      
20        // DFS to count edges that need to be reversed
21        // currentNode: current node being visited
22        // parentNode: parent node to avoid revisiting
23        function<int(int, int)> countReversals = [&](int currentNode, int parentNode) -> int {
24            int reversalCount = 0;
25          
26            // Explore all neighbors of current node
27            for (const auto& [neighborNode, needsReversal] : adjacencyList[currentNode]) {
28                // Skip parent node to avoid going back
29                if (neighborNode != parentNode) {
30                    // Add reversal cost if edge needs to be reversed
31                    // Recursively count reversals in subtree
32                    reversalCount += needsReversal + countReversals(neighborNode, currentNode);
33                }
34            }
35          
36            return reversalCount;
37        };
38      
39        // Start DFS from node 0 with no parent (-1)
40        return countReversals(0, -1);
41    }
42};
43
1/**
2 * Minimum number of edges to reorder so all paths lead to city 0
3 * @param n - Number of cities (nodes)
4 * @param connections - Array of directed edges [from, to]
5 * @returns Minimum number of edges that need to be reversed
6 */
7function minReorder(n: number, connections: number[][]): number {
8    // Build adjacency list with bidirectional edges
9    // Each edge stores [destination, cost] where cost = 1 if edge needs reversal, 0 otherwise
10    const adjacencyList: [number, number][][] = Array.from({ length: n }, () => []);
11  
12    // Process each connection
13    for (const [from, to] of connections) {
14        // Original edge: from -> to (needs reversal if traversed, cost = 1)
15        adjacencyList[from].push([to, 1]);
16        // Reverse edge: to -> from (doesn't need reversal, cost = 0)
17        adjacencyList[to].push([from, 0]);
18    }
19  
20    /**
21     * Depth-first search to count edges that need reversal
22     * @param currentNode - Current node being visited
23     * @param parentNode - Parent node to avoid revisiting
24     * @returns Total number of edges that need reversal in this subtree
25     */
26    const countReversals = (currentNode: number, parentNode: number): number => {
27        let reversalCount = 0;
28      
29        // Visit all neighbors except the parent
30        for (const [neighbor, needsReversal] of adjacencyList[currentNode]) {
31            if (neighbor !== parentNode) {
32                // Add reversal cost for this edge and recursively process subtree
33                reversalCount += needsReversal + countReversals(neighbor, currentNode);
34            }
35        }
36      
37        return reversalCount;
38    };
39  
40    // Start DFS from city 0 with no parent (-1)
41    return countReversals(0, -1);
42}
43

Time and Space Complexity

Time Complexity: O(n)

The algorithm builds an adjacency list representation of the graph and then performs a depth-first search (DFS) starting from node 0.

  • Building the graph takes O(n-1) time since there are n-1 connections (edges) in a tree with n nodes. For each connection, we add two entries to the adjacency list in constant time.
  • The DFS visits each node exactly once. At each node, we iterate through its neighbors. Since this is a tree structure with n nodes and n-1 edges, each edge is traversed exactly twice (once from each direction). The total work done across all nodes is O(n + 2(n-1)) = O(n).

Space Complexity: O(n)

The space usage consists of:

  • The adjacency list g which stores 2(n-1) entries total across all nodes, requiring O(n) space.
  • The recursive call stack for DFS, which in the worst case (a linear tree) can go up to depth n, requiring O(n) space.
  • The function parameters and local variables use constant space per call.

Therefore, the overall space complexity is O(n).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Misunderstanding the Direction Logic

A common mistake is getting confused about when an edge needs to be reversed. Many people incorrectly think:

  • "If we traverse from a to b in DFS, we need to reverse the edge"

The Correct Logic:

  • When DFS traverses from city a to city b, we're exploring outward from city 0
  • If the original edge was aβ†’b, then this edge points AWAY from city 0, so it needs reversing (cost=1)
  • If the original edge was bβ†’a, then this edge already points TOWARD city 0, so no reversal needed (cost=0)

Wrong Implementation:

# INCORRECT: Confusing the reversal logic
for from_city, to_city in connections:
    adjacency_list[from_city].append((to_city, 0))  # Wrong!
    adjacency_list[to_city].append((from_city, 1))  # Wrong!

Correct Implementation:

for from_city, to_city in connections:
    adjacency_list[from_city].append((to_city, 1))  # Correct: traversing along original direction needs reversal
    adjacency_list[to_city].append((from_city, 0))  # Correct: traversing against original direction doesn't need reversal

2. Forgetting to Track Visited Nodes

Without tracking the parent/visited nodes, the DFS will infinite loop:

Wrong Implementation:

def count_reversals(current_city: int) -> int:
    total = 0
    for neighbor, cost in adjacency_list[current_city]:
        # This will revisit the parent and cause infinite recursion!
        total += cost + count_reversals(neighbor)
    return total

Correct Implementation:

def count_reversals(current_city: int, parent_city: int) -> int:
    total = 0
    for neighbor, cost in adjacency_list[current_city]:
        if neighbor != parent_city:  # Skip parent to avoid cycles
            total += cost + count_reversals(neighbor, current_city)
    return total

3. Building Only Unidirectional Graph

Some might try to build the graph with only the original edges:

Wrong Implementation:

# Only adding original edges - DFS can't traverse the full tree!
for from_city, to_city in connections:
    adjacency_list[from_city].append(to_city)

This fails because DFS starting from city 0 can't reach cities that have edges pointing toward it. The bidirectional representation is essential for complete traversal.

4. Using Wrong Data Structure

Using a dictionary with single values instead of lists for multiple neighbors:

Wrong Implementation:

# This overwrites previous neighbors!
adjacency_list = {}
for from_city, to_city in connections:
    adjacency_list[from_city] = (to_city, 1)  # Overwrites previous neighbor
    adjacency_list[to_city] = (from_city, 0)  # Overwrites previous neighbor

Correct Implementation:

adjacency_list = [[] for _ in range(n)]
for from_city, to_city in connections:
    adjacency_list[from_city].append((to_city, 1))
    adjacency_list[to_city].append((from_city, 0))
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

In a binary min heap, the minimum element can be found in:


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More