Before attempting this problem, you should be comfortable with:
Graph Representation - Building adjacency lists from edge lists to represent graph connections
Depth First Search (DFS) - Recursively traversing all nodes in a graph while tracking visited nodes
Breadth First Search (BFS) - Level-by-level graph traversal using a queue
Hash Set - Efficiently storing and checking edge directions in O(1) time
1. Depth First Search - I
Intuition
We need all cities to reach city 0, so we traverse outward from city 0 and check edge directions. Build an undirected neighbors graph for traversal, but store original edges in edges set to check direction. When moving from city A to neighbor B, if the original edge goes from A to B (away from 0), it needs to be reversed. dfs ensures we visit every city exactly once.
Algorithm
Store all original directed edges in a set called edges as (a, b) pairs.
Build an adjacency list called neighbors treating edges as undirected.
Start dfs from city 0, marking visited cities in visit set.
For each neighbor:
If not visited, check if (neighbor, city) exists in the edges set.
If not, the edge points away from 0 and needs reversal; increment changes.
classSolution:defminReorder(self, n:int, connections: List[List[int]])->int:
edges ={(a, b)for a, b in connections}
neighbors ={city:[]for city inrange(n)}
visit =set()
changes =0for a, b in connections:
neighbors[a].append(b)
neighbors[b].append(a)defdfs(city):nonlocal changes
visit.add(city)for neighbor in neighbors[city]:if neighbor in visit:continueif(neighbor, city)notin edges:
changes +=1
dfs(neighbor)
dfs(0)return changes
publicclassSolution{privateDictionary<int, List<int>> neighbors;privatebool[] visit;privateHashSet<string> edges;publicintMinReorder(int n,int[][] connections){
edges =newHashSet<string>();
neighbors =newDictionary<int, List<int>>();
visit =newbool[n];int changes =0;for(int i =0; i < n; i++){
neighbors[i]=newList<int>();}foreach(var conn in connections){int a = conn[0], b = conn[1];
edges.Add($"{a},{b}");
neighbors[a].Add(b);
neighbors[b].Add(a);}voidDfs(int city){
visit[city]=true;foreach(int neighbor in neighbors[city]){if(visit[neighbor])continue;if(!edges.Contains($"{neighbor},{city}")) changes++;Dfs(neighbor);}}Dfs(0);return changes;}}
funcminReorder(n int, connections [][]int)int{
edges :=make(map[string]bool)
neighbors :=make([][]int, n)
visit :=make([]bool, n)
changes :=0for i :=0; i < n; i++{
neighbors[i]=[]int{}}for_, conn :=range connections {
a, b := conn[0], conn[1]
edges[fmt.Sprintf("%d,%d", a, b)]=true
neighbors[a]=append(neighbors[a], b)
neighbors[b]=append(neighbors[b], a)}var dfs func(city int)
dfs =func(city int){
visit[city]=truefor_, neighbor :=range neighbors[city]{if visit[neighbor]{continue}if!edges[fmt.Sprintf("%d,%d", neighbor, city)]{
changes++}dfs(neighbor)}}dfs(0)return changes
}
class Solution {funminReorder(n: Int, connections: Array<IntArray>): Int {val edges = HashSet<String>()val neighbors =Array(n){ mutableListOf<Int>()}val visit =BooleanArray(n)var changes =0for(conn in connections){val(a, b)= conn[0]to conn[1]
edges.add("$a,$b")
neighbors[a].add(b)
neighbors[b].add(a)}fundfs(city: Int){
visit[city]=truefor(neighbor in neighbors[city]){if(visit[neighbor])continueif(!edges.contains("$neighbor,$city")) changes++dfs(neighbor)}}dfs(0)return changes
}}
classSolution{funcminReorder(_ n:Int,_ connections:[[Int]])->Int{var edges =Set<String>()var neighbors =[[Int]](repeating:[], count: n)var visit =[Bool](repeating:false, count: n)var changes =0for conn in connections {let a = conn[0], b = conn[1]
edges.insert("\(a),\(b)")
neighbors[a].append(b)
neighbors[b].append(a)}funcdfs(_ city:Int){
visit[city]=truefor neighbor in neighbors[city]{if visit[neighbor]{continue}if!edges.contains("\(neighbor),\(city)"){ changes +=1}dfs(neighbor)}}dfs(0)return changes
}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n)
2. Depth First Search - II
Intuition
Instead of using a separate set to track edge directions, we can encode direction information directly in the adjacency list. When adding edges, we store the original direction as a positive value and the reverse as negative. During dfs, positive nei values indicate edges pointing away from city 0, which need reversal.
Algorithm
Build an adjacency list called adj where each edge (u, v) adds v to adj[u] and -u to adj[v].
Start dfs from city 0 with parent = -1.
For each nei (neighbor):
Skip if abs(nei) equals the parent (to avoid going back).
Add 1 to the count if nei > 0 (edge needs reversal).
classSolution{public:intminReorder(int n, vector<vector<int>>& connections){
vector<vector<int>>adj(n);for(auto& conn : connections){int u = conn[0], v = conn[1];
adj[u].push_back(v);
adj[v].push_back(-u);}returndfs(0,-1, adj);}private:intdfs(int node,int parent, vector<vector<int>>& adj){int changes =0;for(int nei : adj[node]){if(abs(nei)== parent)continue;
changes +=dfs(abs(nei), node, adj)+(nei >0);}return changes;}};
classSolution{/**
* @param {number} n
* @param {number[][]} connections
* @return {number}
*/minReorder(n, connections){const adj = Array.from({length: n },()=>[]);for(const[u, v]of connections){
adj[u].push(v);
adj[v].push(-u);}constdfs=(node, parent)=>{let changes =0;for(const nei of adj[node]){if(Math.abs(nei)=== parent)continue;
changes +=dfs(Math.abs(nei), node)+(nei >0?1:0);}return changes;};returndfs(0,-1);}}
publicclassSolution{publicintMinReorder(int n,int[][] connections){var adj =newList<int>[n];for(int i =0; i < n; i++) adj[i]=newList<int>();foreach(var conn in connections){
adj[conn[0]].Add(conn[1]);
adj[conn[1]].Add(-conn[0]);}intDfs(int node,int parent){int changes =0;foreach(int nei in adj[node]){if(Math.Abs(nei)== parent)continue;
changes +=Dfs(Math.Abs(nei), node)+(nei >0?1:0);}return changes;}returnDfs(0,-1);}}
funcminReorder(n int, connections [][]int)int{
adj :=make([][]int, n)for i :=0; i < n; i++{
adj[i]=[]int{}}for_, conn :=range connections {
u, v := conn[0], conn[1]
adj[u]=append(adj[u], v)
adj[v]=append(adj[v],-u)}var dfs func(node, parent int)int
dfs =func(node, parent int)int{
changes :=0for_, nei :=range adj[node]{
absNei := nei
if absNei <0{
absNei =-absNei
}if absNei == parent {continue}
add :=0if nei >0{
add =1}
changes +=dfs(absNei, node)+ add
}return changes
}returndfs(0,-1)}
class Solution {funminReorder(n: Int, connections: Array<IntArray>): Int {val adj =Array(n){ mutableListOf<Int>()}for(conn in connections){
adj[conn[0]].add(conn[1])
adj[conn[1]].add(-conn[0])}fundfs(node: Int, parent: Int): Int {var changes =0for(nei in adj[node]){if(kotlin.math.abs(nei)== parent)continue
changes +=dfs(kotlin.math.abs(nei), node)+if(nei >0)1else0}return changes
}returndfs(0,-1)}}
classSolution{funcminReorder(_ n:Int,_ connections:[[Int]])->Int{var adj =[[Int]](repeating:[], count: n)for conn in connections {
adj[conn[0]].append(conn[1])
adj[conn[1]].append(-conn[0])}funcdfs(_ node:Int,_ parent:Int)->Int{var changes =0for nei in adj[node]{ifabs(nei)== parent {continue}
changes +=dfs(abs(nei), node)+(nei >0?1:0)}return changes
}returndfs(0,-1)}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n)
3. Breadth First Search
Intuition
bfs works equally well since we just need to visit all nodes once from city 0. We store each edge with a flag isForward indicating if it is a forward edge (pointing away from city 0). Processing level by level in queue, whenever we traverse a forward edge, we count it as needing reversal.
Algorithm
Build an adjacency list called adj where each edge (u, v) stores (v, 1) in adj[u] and (u, 0) in adj[v].
Initialize a queue with city 0 and mark it visited in visit array.
While the queue is not empty:
Dequeue a node.
For each unvisited neighbor, mark it visited, add isForward to the count, and enqueue it.
A common mistake is building only a directed adjacency list based on the given edges. Since we need to traverse the entire tree starting from city 0, we must treat edges as undirected for traversal purposes while separately tracking the original direction to count reversals.
Reversing the Logic of Edge Direction Check
When checking if an edge needs reversal, it is easy to get the condition backwards. Remember that edges pointing away from city 0 need reversal. If traversing from node A to neighbor B and the original edge is (A, B), it points away from 0 and must be counted; if the original edge is (B, A), it already points toward 0.
Not Handling the Sign Encoding for Node 0
In the DFS-II approach that uses negative values to encode direction, node 0 cannot be represented as negative since -0 == 0. This edge case must be handled by using the parent check (abs(nei) == parent) rather than relying solely on sign for all nodes.