Problem overview
The method bfsMinDistance receives the number of vertices n, labelled 0 to n - 1, and a vector edges of pairs [u, v]. It returns a vector dist of length n in which dist[i] is the smallest number of edges on a route from vertex 0 to vertex i, or -1 when no route exists.
For n = 5 and edges = [[0,1],[0,2],[1,3],[2,4]], vertex 0 is at distance 0, vertices 1 and 2 are one edge away, and vertices 3 and 4 are two edges away. The answer is [0, 1, 1, 2, 2].
Approach: breadth-first search
All edges count the same, so the nearest vertices are the ones reachable in one step, then the ones reachable in two, and so on. Breadth-first search (BFS) visits vertices in exactly that order, in rings around the source: distance 0, then distance 1, then 2.
The tool that produces this order is a queue, a first-in, first-out line. Start with the source in the queue. Repeatedly take the vertex at the front, look at its neighbours, and add every neighbour not seen before to the back. Vertices discovered earlier sit nearer the front and are processed first, so all vertices at distance d come out before any at distance d + 1.
That order gives the key property: the first time BFS reaches a vertex, it has taken a shortest route. So the distance can be written down immediately and never changed. The dist vector doubles as the visited record: a value of -1 means "not reached yet".
On the example, the queue goes: [0]. Take 0 and reach 1 and 2 (distance 1) giving [1, 2]. Take 1 and reach 3 (distance 2). Take 2 and reach 4 (distance 2). Then 3 and 4 have no new neighbours and the queue empties.
Step by step
- Build the adjacency list
graphas anunordered_map<int, vector<int>>, mapping each vertex to its neighbours. The first use ofgraph[x]creates an empty vector forx, so there's no setup loop. - Fill it from
edges. Loop withauto& edgeand pushedge[1]ontograph[edge[0]]. This code records each pair in one direction, fromedge[0]toedge[1]. - Create the queue
qas aqueue<int>and push the source, 0. - Create
minDistwithnentries of-1, then setminDist[0]to 0, because the source is zero edges from itself. - Loop while the queue is not empty.
- Take the next vertex: read
q.front()intonode, then callq.pop(). Two calls are needed becausepopreturns nothing. - Visit its neighbours with a range-based loop over
graph[node], naming each oneadjNode. - Claim unreached neighbours. If
minDist[adjNode]is still-1, set it tominDist[node] + 1and pushadjNodeonto the queue. The-1test means a vertex enters the queue at most once. - Return
minDist. Vertices that BFS never reached keep their-1.
The solution in C++
class Solution {
public:
vector<int> bfsMinDistance(int n, vector<vector<int>>& edges) {
unordered_map<int, vector<int>> graph;
for (auto& edge : edges) {
graph[edge[0]].push_back(edge[1]);
}
queue<int> q;
q.push(0);
vector<int> minDist(n, -1);
minDist[0] = 0;
while (!q.empty()) {
int node = q.front();
q.pop();
for (int adjNode : graph[node]) {
if (minDist[adjNode] == -1) {
minDist[adjNode] = minDist[node] + 1;
q.push(adjNode);
}
}
}
return minDist;
}
};
The C++ details to notice:
unordered_map::operator[]inserts. Readinggraph[node]for a key that is missing creates an empty vector and returns it. That is what makes the push-back loop work without setup, and it also makes the neighbour loop safe for a vertex with no outgoing edges. The price is that a plain lookup can grow the map. When you only want to check, usefindorcount. Avector<vector<int>>of sizenwould also work, and it is faster.vector<int> minDist(n, -1)uses the constructor that takes a size and a fill value, so no loop is needed.front()thenpop().std::queuekeeps reading and removing apart. Copy the front value intonodebefore you pop, since the reference returned byfront()is not valid after the pop.- Direction of edges. The code adds only
edge[0] → edge[1]. That matches the example, where each edge is listed with the nearer vertex first. To treat every edge as two-way, which is what an undirected graph needs, add a second line inside the loop:graph[edge[1]].push_back(edge[0]);. With that line, the example still gives[0, 1, 1, 2, 2], and an input liken = 3,edges = [[1,0]]gives[0, 1, -1]instead of[0, -1, -1]. - Index types.
nand the vertices areint, sominDist[adjNode]indexes the vector with anint. That's fine for non-negative values.
Complexity
Time: O(n + m), where m is the number of edges. Building the map touches each edge once. Each vertex enters the queue at most once, and the neighbour loops scan each stored edge once in total.
Space: O(n + m). The adjacency map holds every stored edge, and minDist and the queue each hold at most n vertices.
Edge cases and common mistakes
- The source itself.
minDist[0]must be set to 0 before the loop, or the source would look unreached and could be queued again. - Unreachable vertices. They keep
-1, which is the required answer. Initialising the vector with 0 instead would make them look like they were at the source. - Using a stack instead of a queue. That gives depth-first order, and the first route found is often not the shortest.
- Setting the distance when popping. Marking vertices only when they leave the queue lets the same vertex be pushed several times. Set the distance at the moment of pushing.
- Cycles. A vertex reached again is skipped, because its distance is no longer
-1, so cycles can't cause an endless loop. - Isolated source. If vertex 0 has no neighbours, the loop runs once and the result is 0 followed by
-1entries.
Testing
n = 5,edges = [[0,1],[0,2],[1,3],[2,4]]gives[0, 1, 1, 2, 2].n = 3,edges = [[0,1]]gives[0, 1, -1].- With the extra reverse-edge line added,
n = 3,edges = [[1,0]]gives[0, 1, -1].