Opening the reading…
Opening the reading…
GRAPHS › DFS AND BFS ON GRAPHS
Open — the attempt gate is not wired up yet
This editorial is meant to unlock after you have run the problem at least once, with the worked solution behind one further deliberate click. That needs per-learner unlock state nothing stores today, so for now the whole article is open.
Try it yourself first →Because the roads form a tree, every city has exactly one path to city 0. Once you imagine city 0 as the root, each road has a required final direction: it must point from a node to its parent, closer to 0. There is no choice between multiple routes, so an edge either already points toward the root or it must be reversed.
The input gives each road in its original direction, but traversal needs to move through the tree in both directions. Store every road twice: once for its original direction and once as a usable undirected connection. Mark the first copy as needing a reversal when you traverse it from the root outward. Then a traversal from 0 counts every edge that points away from 0 exactly once.
| MEASURE | BOUND | WHY |
|---|---|---|
| Time | O(n) | The adjacency list has 2(n - 1) entries, and BFS examines each entry once. Each city enters the queue once because visited is set before enqueueing, so neither the reverse adjacency entries nor any city can cause repeated expansion. |
| Space | O(n) extra | The adjacency list, visited array, and queue together use O(n) working space. The returned count is constant-size output and is excluded. A path-shaped tree is the worst shape for the queue, which can hold O(n) cities; a balanced shape still has O(n) adjacency storage. |
#include <vector>
#include <queue>
#include <utility>
using namespace std;
class Solution {
public:
int minReorder(int n, vector<vector<int>>& connections) {
vector<vector<pair<int, int>>> graph(n);
for (const vector<int>& connection : connections) {
int from = connection[0];
int to = connection[1];
graph[from].push_back({to, 1});
graph[to].push_back({from, 0});
}
int reversals = 0;
vector<bool> visited(n, false);
queue<int> q;
q.push(0);
visited[0] = true;
while (!q.empty()) {
int u = q.front();
q.pop();
for (const pair<int, int>& edge : graph[u]) {
int v = edge.first;
int direction = edge.second;
if (visited[v]) {
continue;
}
if (direction == 1) {
++reversals;
}
visited[v] = true;
q.push(v);
}
}
return reversals;
}
};The two adjacency entries are the central detail. For an original road from to, the entry under from has flag 1 because walking from the root side to the child follows the original arrow. The reverse entry has flag 0 because walking from the child back to the parent is already the direction needed after rooting the tree. The visited check ensures only the parent-to-child encounter classifies the road.
A recursive DFS uses exactly the same orientation test and has the same asymptotic bounds. It is not an optimisation: the adjacency list and visited array still use O(n), while the traversal stack uses O(n) on a path. It is a reasonable alternative when recursive graph code is natural, but the iterative BFS avoids depending on the runtime call stack for the maximum allowed tree depth.
#include <vector>
#include <utility>
using namespace std;
class Solution {
public:
int minReorder(int n, vector<vector<int>>& connections) {
vector<vector<pair<int, int>>> graph(n);
for (const vector<int>& connection : connections) {
int from = connection[0];
int to = connection[1];
graph[from].push_back({to, 1});
graph[to].push_back({from, 0});
}
vector<bool> visited(n, false);
return dfs(0, graph, visited);
}
private:
int dfs(int u, const vector<vector<pair<int, int>>>& graph,
vector<bool>& visited) {
visited[u] = true;
int reversals = 0;
for (const pair<int, int>& edge : graph[u]) {
int v = edge.first;
int direction = edge.second;
if (visited[v]) {
continue;
}
reversals += direction;
reversals += dfs(v, graph, visited);
}
return reversals;
}
};