Start learning free

LESSON 336 OF 348 · GRAPHS

BFS Min Distance to Every Vertex in Kotlin

Practice BFS, O(V+E) Read the full explanation

Given an unweighted undirected graph with n vertices (labeled 0 to n-1) and a list of edges where edges[i] = [u, v], find the minimum distance (number of edges) from vertex 0 to every other vertex using BFS.

Return an array dist of length n where dist[i] is the minimum distance from vertex 0 to vertex i. Use -1 if a vertex is unreachable.

Example

Input: n = 5, edges = [[0,1],[0,2],[1,3],[2,4]]
Output: [0, 1, 1, 2, 2]
Explanation:
- 0 → 0: distance 0
- 0 → 1: distance 1
- 0 → 2: distance 1
- 0 → 1 → 3: distance 2
- 0 → 2 → 4: distance 2

Constraints

  • 2 <= n <= 1000
  • 1 <= edges.length <= 5000
  • 0 <= u, v < n
  • No self-loops or duplicate edges.
STEP 1 OF 9
    Ctrl + Enter to check

    KOTLIN · GRAPHS

    Bfs Min Distance To Every Vertex in Kotlin, explained

    Problem overview

    The function bfsMinDistance receives n, the number of vertices labeled 0 to n - 1, and edges, an Array<IntArray> of pairs [u, v]. It returns an IntArray of length n in which entry i is the minimum number of edges on a path from vertex 0 to vertex i, or -1 when vertex i can't be reached.

    For n = 5 and edges = [[0, 1], [0, 2], [1, 3], [2, 4]] the result is [0, 1, 1, 2, 2]. Vertex 0 is at distance 0 from itself, vertices 1 and 2 are one edge away, and vertices 3 and 4 are two edges away, through 1 and 2.

    Approach: breadth-first search

    Breadth-first search (BFS) explores a graph in rings. First it visits the start vertex, then everything one edge away, then everything two edges away, and so on. Because of that order, the first time BFS reaches a vertex is along a shortest path, in an unweighted graph where every edge counts as 1. So the first distance written for a vertex is its final answer, and no vertex is ever updated twice.

    The ring order comes from a queue. Vertices leave the queue in the order they entered it, so every vertex at distance d is processed before any vertex at distance d + 1. Taking vertices from the wrong end of the queue would give depth-first order, and the distances would come out wrong.

    The distance array doubles as the record of visited vertices. It starts filled with -1, meaning "not reached yet". When BFS meets a neighbour whose entry is still -1, it writes the distance and enqueues the neighbour. Anything BFS never reaches keeps -1, which is exactly what the problem wants for unreachable vertices.

    The function also needs fast neighbour lookup, so it first turns the list of edges into an adjacency list: for every vertex, the list of vertices it leads to.

    Step by step

    1. Create the adjacency list. graph is an array of n empty mutable lists, one per vertex. The Array(n) { ... } constructor runs its lambda once per index, so each vertex gets its own list.
    2. Fill it from edges. For each edge, add edge[1] to the list of edge[0]. This takes time proportional to the number of edges.
    3. Create the queue as an ArrayDeque and put vertex 0 in it. Adding goes to the back and removeFirst takes from the front, both in constant time.
    4. Create minDist as an IntArray of size n filled with -1, then set minDist[0] to 0 because the start is zero edges from itself.
    5. Loop while the queue is not empty. Each vertex enters the queue at most once, so the loop runs at most n times.
    6. Take the front vertex into node with removeFirst.
    7. Loop over its neighbours, calling each one adjNode. Across the whole search this looks at every stored edge once.
    8. Handle unreached neighbours only. If minDist[adjNode] is -1, set it to minDist[node] + 1 and enqueue adjNode.
    9. Return minDist.

    The solution in Kotlin

    class Solution {
        fun bfsMinDistance(n: Int, edges: Array<IntArray>): IntArray {
            val graph = Array(n) { mutableListOf<Int>() }
            for (edge in edges) {
                graph[edge[0]].add(edge[1])
            }
            val queue = ArrayDeque<Int>()
            queue.add(0)
            val minDist = IntArray(n) { -1 }
            minDist[0] = 0
            while (queue.isNotEmpty()) {
                val node = queue.removeFirst()
                for (adjNode in graph[node]) {
                    if (minDist[adjNode] == -1) {
                        minDist[adjNode] = minDist[node] + 1
                        queue.add(adjNode)
                    }
                }
            }
            return minDist
        }
    }
    

    The Kotlin details worth a closer look:

    • IntArray(n) { -1 } creates a primitive int array whose every element is the lambda's result. Without the lambda, IntArray(n) would be filled with 0, which would wrongly say "distance 0" for every vertex.
    • Array(n) { mutableListOf<Int>() } runs the lambda once per index, giving n separate lists. The array's type is Array<MutableList<Int>>.
    • ArrayDeque is Kotlin's own double-ended queue. add appends at the back, removeFirst() takes from the front, and isNotEmpty() drives the loop.
    • Edge direction in this code. Only graph[edge[0]].add(edge[1]) is executed, so the search follows each edge from its first endpoint to its second. For an edge to work in both directions, the loop also needs graph[edge[1]].add(edge[0]). With the code as given, n = 3 and edges = [[1, 0], [1, 2]] returns [0, -1, -1], because nothing leads out of vertex 0.
    • val node is a fresh read-only value in each iteration, while the arrays and lists it indexes remain mutable.

    Complexity

    Time: O(n + m), where m is the number of edges. Building the lists costs O(n + m). In the search, each vertex is enqueued at most once and each stored edge is examined once.

    Space: O(n + m). The adjacency list holds one entry per stored edge, and the distance array and queue hold at most n entries each.

    Edge cases and common mistakes

    • Unreachable vertices. With n = 5 and edges = [[0, 1], [2, 3]] the result is [0, 1, -1, -1, -1]. Vertices 2, 3 and 4 are never enqueued, so they keep -1.
    • No edges. With n = 2 and no edges the result is [0, -1]. The start always has distance 0.
    • Cycles. In a triangle such as [[0, 1], [1, 2], [2, 0]], vertex 0 already has distance 0, so the edge back to it is ignored and the search can't loop forever. The distances come out as [0, 1, 2].
    • Shortcuts. For n = 4 and [[0, 1], [1, 2], [0, 2], [2, 3]], vertex 2 gets distance 1 through the direct edge, not 2 through vertex 1, and vertex 3 gets 2. The result is [0, 1, 1, 2].
    • Using a stack instead of a queue. Removing from the back visits vertices in depth-first order, and the first distance written is no longer the shortest.
    • Checking for visited after the fact. Marking a vertex when it is enqueued, not when it is dequeued, keeps the queue free of duplicates.
    • Initialising with 0. A zero-filled array can't tell "unreached" from "distance 0".

    Testing

    • n = 5, [[0, 1], [0, 2], [1, 3], [2, 4]] gives [0, 1, 1, 2, 2].
    • n = 5, [[0, 1], [2, 3]] gives [0, 1, -1, -1, -1].
    • n = 4, [[0, 1], [1, 2], [0, 2], [2, 3]] gives [0, 1, 1, 2].
    • n = 4, [[0, 1], [1, 2], [2, 0]] gives [0, 1, 2, -1].
    • n = 2, no edges, gives [0, -1].
    Keep going with KotlinAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free