Minimum Edge Reversals So Every Node Is Reachable
HardPrompt
There is a simple directed graph with n nodes labeled from 0 to n - 1. The graph would form a tree if its edges were bi-directional.
You are given an integer n and a 2D integer array edges, where edges[i] = [ui, vi] represents a directed edge going from node ui to node vi.
An edge reversal changes the direction of an edge, i.e., a directed edge going from node ui to node vi becomes a directed edge going from node vi to node ui.
For every node i in the range [0, n - 1], your task is to independently calculate the minimum number of edge reversals required so it is possible to reach any other node starting from node i through a sequence of directed edges.
Return an integer array answer, where answer[i] is the minimum number of edge reversals required so it is possible to reach any other node starting from node i through a sequence of directed edges.
Example 1:

Input: n = 4, edges = [[2,0],[2,1],[1,3]]
Output: [1,1,0,2]
Explanation: The image above shows the graph formed by the edges.
For node 0: after reversing the edge [2,0], it is possible to reach any other node starting from node 0.
So, answer[0] = 1.
For node 1: after reversing the edge [2,1], it is possible to reach any other node starting from node 1.
So, answer[1] = 1.
For node 2: it is already possible to reach any other node starting from node 2.
So, answer[2] = 0.
For node 3: after reversing the edges [1,3] and [2,1], it is possible to reach any other node starting from node 3.
So, answer[3] = 2.Example 2:

Input: n = 3, edges = [[1,2],[2,0]]
Output: [2,0,1]
Explanation: The image above shows the graph formed by the edges.
For node 0: after reversing the edges [2,0] and [1,2], it is possible to reach any other node starting from node 0.
So, answer[0] = 2.
For node 1: it is already possible to reach any other node starting from node 1.
So, answer[1] = 0.
For node 2: after reversing the edge [1, 2], it is possible to reach any other node starting from node 2.
So, answer[2] = 1.
Constraints:
2 <= n <= 105edges.length == n - 1edges[i].length == 20 <= ui == edges[i][0] < n0 <= vi == edges[i][1] < nui != vi- The input is generated such that if the edges were bi-directional, the graph would be a tree.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach iterates through each node from 0 to n-1, treating each one as a potential root. For each potential root, it calculates the minimum number of edge reversals required to make all other nodes reachable.
Algorithm
- Create an adjacency list for the undirected version of the graph.
- Store the original directed edges in a
HashSetfor efficient lookups. - Iterate through each node
ifrom0ton-1.- For each
i, perform a Breadth-First Search (BFS) or Depth-First Search (DFS) starting fromi. - Initialize a
reversalscount to 0. - During the traversal from a node
uto a neighborv, check if the original edge wasv -> u. - If it was, increment the
reversalscount. - After the traversal is complete, store the total
reversalsinanswer[i].
- For each
- Return the
answerarray.
Walkthrough
To make all nodes reachable from a starting node i, the graph must form a directed tree rooted at i, where all edges point away from the root. The underlying undirected graph is a tree, so there's a unique path from i to any other node j. We need to ensure all edges on these paths are directed away from i.
The algorithm for each potential root i is as follows:
- Initialize a counter for reversals to zero.
- Perform a graph traversal (like BFS or DFS) starting from
ion the underlying undirected graph structure. - To do this, we first build an undirected adjacency list from the given directed edges. We also need a way to check the original direction of an edge, for example, by storing the directed edges in a
HashSetof pairs. - During the traversal, whenever we move from a node
uto a neighborv, we check the original direction of the edge between them. - If the original edge was
v -> u, it's pointing towards the rootiinstead of away from it. This edge must be reversed, so we increment the reversal counter. - After the traversal completes, the counter holds the total number of reversals needed for
ito be the root. This value is stored inanswer[i]. - This process is repeated for all
nnodes.
import java.util.*; class Solution { public int[] minEdgeReversals(int n, int[][] edges) { // Store original directed edges for quick lookup Set<String> originalEdges = new HashSet<>(); List<List<Integer>> adj = new ArrayList<>(); for (int i = 0; i < n; i++) { adj.add(new ArrayList<>()); } for (int[] edge : edges) { int u = edge[0]; int v = edge[1]; originalEdges.add(u + "," + v); adj.get(u).add(v); adj.get(v).add(u); } int[] answer = new int[n]; for (int i = 0; i < n; i++) { answer[i] = countReversalsForRoot(i, n, adj, originalEdges); } return answer; } private int countReversalsForRoot(int root, int n, List<List<Integer>> adj, Set<String> originalEdges) { int reversals = 0; Queue<Integer> queue = new LinkedList<>(); boolean[] visited = new boolean[n]; queue.offer(root); visited[root] = true; while (!queue.isEmpty()) { int u = queue.poll(); for (int v : adj.get(u)) { if (!visited[v]) { visited[v] = true; // We are traversing from u to v. // The edge should be u -> v. // If the original edge is v -> u, we need a reversal. if (originalEdges.contains(v + "," + u)) { reversals++; } queue.offer(v); } } } return reversals; }}Complexity
Time
O(N^2). For each of the `N` nodes, we perform a full graph traversal (BFS/DFS), which takes `O(N + E)` time. Since `E = N-1`, this is `O(N)`. Repeating this `N` times gives `O(N^2)`.
Space
O(N). We need `O(N)` space for the adjacency list, `O(N)` for the `HashSet` of edges, and `O(N)` for the BFS queue and `visited` array.
Trade-offs
Pros
Simple to understand and implement.
Correctly solves the problem by simulating the process for each node.
Cons
Inefficient due to re-computation. The
O(N^2)time complexity is too slow for the given constraints (n <= 10^5).
This approach avoids redundant calculations by using a dynamic programming technique on the tree, often called "rerooting". It consists of two main passes (DFS traversals). The first pass calculates the answer for an arbitrary root (e.g., node 0). The second pass uses this result to efficiently compute the answers for all other nodes in linear time.
Algorithm
- Build an adjacency list for the graph. For each directed edge
u -> v, addvtou's neighbors with acostof 0, and addutov's neighbors with acostof 1 (indicating a reversal is needed to traversev -> u). - Initialize an
answerarray of sizen. - First Pass (DFS):
- Perform a DFS from an arbitrary root (e.g., node 0).
- This DFS (
dfs1) calculates the total reversals needed if node 0 is the root. - In
dfs1(u, parent), for each childvofu, add the cost of theu -> vtraversal toanswer[0]. Then, recurse on the child.
- Second Pass (DFS):
- Perform another DFS from the same root (node 0).
- This DFS (
dfs2) calculates the answer for each node based on its parent's answer. - In
dfs2(u, parent), for each childvofu:- The answer for
vis derived from the answer foru. When the root moves fromutov, the cost associated with the edge(u, v)flips. - If the original edge was
u -> v,answer[v] = answer[u] + 1. - If the original edge was
v -> u,answer[v] = answer[u] - 1. - Recursively call
dfs2(v, u).
- The answer for
- Return the
answerarray.
Walkthrough
The core idea is that the answers for adjacent nodes are closely related. If we know the number of reversals for a node u to be the root, we can calculate the answer for its neighbor v with a simple update.
First Pass (DFS from an arbitrary root):
- We start by choosing an arbitrary root, say node
0. - We build an adjacency list that represents the undirected tree structure but also encodes the original edge directions. For each original edge
u -> v, we can add(v, 0)tou's adjacency list and(u, 1)tov's list. The second element in the pair represents the cost of traversal:0for following the original direction and1for going against it (a reversal). - We perform a post-order DFS traversal (
dfs1) starting from the root0. This traversal calculatesanswer[0], the number of reversals needed to make0the universal source. During the traversal from a parentuto a childv, if the edgeu -> vis against the original direction (cost is 1), we add 1 to our total count.
Second Pass (Rerooting DFS):
- Now, we perform a second, pre-order DFS traversal (
dfs2) starting from root0to calculate the answers for all other nodes. - When we move from a parent
uto a childv, we can computeanswer[v]based onanswer[u]. - When the root shifts from
utov, the required direction of the edge between them flips.- If the original edge was
u -> v, it was correctly oriented for rootu(cost 0), but needs to be reversed for rootv(cost 1). So,answer[v]will be one more thananswer[u]would be, considering only this edge's change. - If the original edge was
v -> u, it was incorrectly oriented for rootu(cost 1), but is now correctly oriented for rootv(cost 0). So,answer[v]will be one less.
- If the original edge was
- The formula is:
answer[v] = answer[u] + (original edge u->v ? 1 : -1). - We apply this formula recursively down the tree.
import java.util.*; class Solution { List<List<int[]>> adj; int[] answer; public int[] minEdgeReversals(int n, int[][] edges) { adj = new ArrayList<>(); for (int i = 0; i < n; i++) { adj.add(new ArrayList<>()); } for (int[] edge : edges) { int u = edge[0]; int v = edge[1]; // u -> v: cost 0 from u to v, cost 1 from v to u adj.get(u).add(new int[]{v, 0}); adj.get(v).add(new int[]{u, 1}); } answer = new int[n]; // First DFS to calculate answer for root 0 dfs1(0, -1); // Second DFS to calculate answers for all other nodes dfs2(0, -1); return answer; } // DFS to calculate the cost for the tree rooted at 0. private void dfs1(int u, int p) { for (int[] edgeInfo : adj.get(u)) { int v = edgeInfo[0]; int cost = edgeInfo[1]; if (v == p) continue; answer[0] += cost; // cost is 1 if edge is v->u, needs reversal for u->v dfs1(v, u); } } // DFS to compute answers for all nodes based on the parent's answer private void dfs2(int u, int p) { for (int[] edgeInfo : adj.get(u)) { int v = edgeInfo[0]; int cost = edgeInfo[1]; if (v == p) continue; // When moving root from u to v: // If original edge was u->v (cost=0), we need to reverse it. Cost increases by 1. // If original edge was v->u (cost=1), it's now in the correct direction. Cost decreases by 1. // The change is 1 - 2*cost. If cost=0, change=1. If cost=1, change=-1. answer[v] = answer[u] + (1 - 2 * cost); dfs2(v, u); } }}Complexity
Time
O(N). Building the adjacency list takes `O(N)` time. Both DFS traversals visit each node and edge once, taking `O(N + E)` time, which is `O(N)` as `E = N-1`.
Space
O(N). The adjacency list requires `O(N)` space. The recursion stack for DFS can go up to `O(N)` in the worst case (a linear tree). The `answer` array also takes `O(N)` space.
Trade-offs
Pros
Highly efficient with linear time complexity, making it suitable for large inputs.
Elegantly solves the problem by reusing previously computed results.
Cons
The logic, especially the rerooting step, can be less intuitive to grasp compared to the brute-force method.
Solutions
Solution
class Solution {private List<int[]>[] g;private int[] ans;public int[] minEdgeReversals(int n, int[][] edges) { ans = new int[n]; g = new List[n]; Arrays.setAll(g, i->new ArrayList<>()); for (var e : edges) { int x = e[0], y = e[1]; g[x].add(new int[]{y, 1}); g[y].add(new int[]{x, -1}); } dfs(0, -1); dfs2(0, -1); return ans; }private void dfs(int i, int fa) { for (var ne : g[i]) { int j = ne[0], k = ne[1]; if (j != fa) { ans[0] += k < 0 ? 1 : 0; dfs(j, i); } } }private void dfs2(int i, int fa) { for (var ne : g[i]) { int j = ne[0], k = ne[1]; if (j != fa) { ans[j] = ans[i] + k; dfs2(j, i); } } }}class Solution {public: vector<int> minEdgeReversals(int n, vector<vector<int>> &edges) { vector<pair<int, int>> g[n]; vector<int> ans(n); for (auto &e : edges) { int x = e[0], y = e[1]; g[x].emplace_back(y, 1); g[y].emplace_back(x, -1); } function<void(int, int)> dfs = [&](int i, int fa) { for (auto &[j, k] : g[i]) { if (j != fa) { ans[0] += k < 0; dfs(j, i); } } }; function<void(int, int)> dfs2 = [&](int i, int fa) { for (auto &[j, k] : g[i]) { if (j != fa) { ans[j] = ans[i] + k; dfs2(j, i); } } }; dfs(0, -1); dfs2(0, -1); return ans; }};class Solution: def minEdgeReversals(self, n: int, edges: List[List[int]]) -> List[int]: ans = [0] * n g = [[] for _ in range(n)] for x, y in edges: g[x]. append((y, 1)) g[y]. append((x, - 1)) def dfs(i: int, fa: int): for j, k in g[i]: if j != fa: ans[0] += int(k < 0) dfs(j, i) dfs(0, - 1) def dfs2(i: int, fa: int): for j, k in g[i]: if j != fa: ans[j] = ans[i] + k dfs2(j, i) dfs2(0, - 1) return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.