Problem overview
The method bfsMinDistance receives n, the number of vertices labeled 0 to n - 1, and a list of edges, where each edge is a pair [u, v]. It returns an array dist of length n. Entry dist[i] is the minimum number of edges on a path from vertex 0 to vertex i, or -1 if vertex i can't be reached.
For n = 5 and edges = [[0,1],[0,2],[1,3],[2,4]] the answer is [0, 1, 1, 2, 2]. Vertex 0 is at distance 0 from itself. Vertices 1 and 2 are one edge away. Vertex 3 is reached through 1 and vertex 4 through 2, two edges each.
Approach: explore in rings around the source
Because every edge counts as 1, the distance to a vertex is simply the number of steps in the shortest walk. Breadth-first search (BFS) finds those walks naturally. It visits vertex 0 first, then everything one edge away, then everything two edges away, and so on, ring by ring. The first time BFS reaches a vertex is therefore along a shortest path, so that is the distance to record, and it never needs updating.
The ring-by-ring order comes from a queue, which hands out items first in, first out. Vertices are enqueued as they are discovered, and they are processed in the order they were discovered, so all vertices at distance d are processed before any at distance d + 1. A depth-first search, which uses a stack or recursion, would dive deep along one branch and could reach a vertex by a long route first, so it doesn't give shortest distances.
The distance array doubles as the visited marker. It starts filled with -1, meaning "not reached yet". Vertices that BFS never reaches keep that -1, which is exactly what the problem wants for unreachable vertices.
Step by step
- Create the adjacency list.
graphholds one emptynumber[]per vertex, built withArray.fromso that each vertex gets its own array. Looking up a vertex's neighbours then takes constant time. - Fill it from
edges. For eachedge, pushedge[1]ontograph[edge[0]]. - Create the queue as a
number[]that starts with vertex 0. An array works as a queue:pushadds at the back andshiftremoves from the front. - Create
minDistwithnentries, all-1, and setminDist[0]to 0. The source is zero edges from itself. - Loop while the queue is not empty. Each vertex is enqueued at most once, so the loop runs at most
ntimes. - Take the front vertex with
shift()intonode. The result type isnumber | undefined, becauseshiftreturnsundefinedon an empty array, so the code adds!to tell the compiler that the loop condition guarantees a value. - Loop over the neighbours of
nodewithadjNodeas the variable. - Handle a new vertex. If
minDist[adjNode]is still-1, set it tominDist[node] + 1and pushadjNodeonto the queue. - Return
minDist.
The solution in TypeScript
class Solution {
bfsMinDistance(n: number, edges: number[][]): number[] {
const graph: number[][] = Array.from({length: n}, () => []);
for (const edge of edges) {
graph[edge[0]].push(edge[1]);
}
const queue: number[] = [0];
const minDist: number[] = new Array(n).fill(-1);
minDist[0] = 0;
while (queue.length > 0) {
const node: number = queue.shift()!;
for (const adjNode of graph[node]) {
if (minDist[adjNode] === -1) {
minDist[adjNode] = minDist[node] + 1;
queue.push(adjNode);
}
}
}
return minDist;
}
}
The details worth knowing:
- Fresh arrays per slot.
Array.from({length: n}, () => [])runs the function once per index.new Array(n).fill([])would store a single array in every slot, so a push to one vertex's list would appear in all of them. new Array(n).fill(-1).new Array(n)only reservesnempty slots;fillgives each a value. The result is[-1, -1, ...].- The
!is a compile-time promise. The non-null assertion changes nothing in the compiled JavaScript and checks nothing at run time. Here the promise is true, sincequeue.length > 0was just tested. ===against-1. The strict comparison is the idiomatic way to test for the sentinel.- The graph is read in the listed direction. The loop that builds
graphpushes onlyedge[1]onto the list ofedge[0], so a move from a vertex is possible only along an edge whose first number is that vertex. For the edge lists in this lesson, which name the nearer vertex first, that gives the right distances. For input where an edge may be written in either order, also pushedge[0]ontograph[edge[1]]. Onn = 3andedges = [[1,0],[1,2]], the code as written returns[0, -1, -1], while two-way edges would give[0, 1, 2]. shiftand performance. Removing from the front of an array has to move the remaining items in principle. Engines optimise this well for the sizes in this lesson (up to 1,000 vertices), but for very large queues an index that walks along the array does the job without removing anything.
Complexity
Let V be the number of vertices and E the number of edges.
Time: O(V + E). Building the list takes O(V + E). Each vertex enters the queue at most once, thanks to the -1 check, and each vertex's neighbour list is scanned once, so every edge is looked at once.
Space: O(V + E). The adjacency list stores every edge, and the queue and the distance array hold at most V entries each.
Edge cases and common mistakes
- Unreachable vertices. With
n = 4andedges = [[0,1],[2,3]], the answer is[0, 1, -1, -1]: the search never leaves the component of vertex 0. - Cycles. A triangle
[[0,1],[1,2],[2,0]]gives[0, 1, 2]. The reverse edge back to 0 is ignored becauseminDist[0]is already 0, not-1. - Shortcuts. With
n = 4andedges = [[0,1],[1,2],[2,3],[0,3]], vertex 3 is at distance 1 through the direct edge, even though the long route is listed earlier. The result is[0, 1, 2, 1]. - Using the back of the queue. Replacing
shift()withpop()turns the queue into a stack and breaks the ring-by-ring order, so distances can come out too large. - Marking a vertex when it is dequeued instead of when it is enqueued. The same vertex could be added several times before it gets processed, and the work grows.
- Forgetting to set the source. If
minDist[0]stayed-1, the search would treat vertex 0 as unvisited and could enqueue it again.
Testing
n = 5,edges = [[0,1],[0,2],[1,3],[2,4]]gives[0, 1, 1, 2, 2].n = 4,edges = [[0,1],[2,3]]gives[0, 1, -1, -1].n = 3,edges = [[0,1],[1,2],[2,0]]gives[0, 1, 2].n = 4,edges = [[0,1],[1,2],[2,3],[0,3]]gives[0, 1, 2, 1].