Start learning free

LESSON 335 OF 348 · GRAPHS

Find if Path Exists in Graph in TypeScript: Depth-First Search

Practice O(V+E) Read the full explanation

There is a bi-directional graph with n vertices, where each vertex is labeled from 0 to n - 1 (inclusive). The edges in the graph are represented as a 2D integer array edges, where each edges[i] = [ui, vi] denotes a bi-directional edge between vertex ui and vertex vi. Every vertex pair is connected by at most one edge, and no vertex has an edge to itself.

You want to determine if there is a valid path that exists from vertex source to vertex destination.

Given edges and the integers n, source, and destination, return true if there is a valid path from source to destination, or false otherwise.

 

Example 1:

Input: n = 3, edges = [[0,1],[1,2],[2,0]], source = 0, destination = 2
Output: true
Explanation: There are two paths from vertex 0 to vertex 2:
- 0 → 1 → 2
- 0 → 2

Example 2:

Input: n = 6, edges = [[0,1],[0,2],[3,5],[5,4],[4,3]], source = 0, destination = 5
Output: false
Explanation: There is no path from vertex 0 to vertex 5.

 

Constraints:

  • 1 <= n <= 2 * 105
  • 0 <= edges.length <= 2 * 105
  • edges[i].length == 2
  • 0 <= ui, vi <= n - 1
  • ui != vi
  • 0 <= source, destination <= n - 1
  • There are no duplicate edges.
  • There are no self edges.
STEP 1 OF 10
    Ctrl + Enter to check

    TYPESCRIPT · GRAPHS

    Find If Path Exist In A Graph in TypeScript, explained

    Problem overview

    The method validPath receives n, the number of vertices labeled 0 to n - 1, a list of edges, and two vertices, source and destination. Each edge [u, v] connects two vertices in both directions. The method returns true if you can walk from source to destination along edges, and false otherwise.

    In the first example, n = 3 and edges = [[0,1],[1,2],[2,0]] form a triangle. From vertex 0 you can reach vertex 2 directly or through vertex 1, so the answer is true. In the second example, n = 6 with edges [[0,1],[0,2],[3,5],[5,4],[4,3]] has two separate groups, {0, 1, 2} and {3, 4, 5}. Vertex 0 can't reach vertex 5, so the answer is false.

    Approach: explore everything reachable from the source

    An edge list is awkward for walking: to find a vertex's neighbours you would scan every edge. So the first step is to build an adjacency list, where entry i holds the neighbours of vertex i.

    Then run a depth-first search (DFS) from source. DFS goes as deep as it can along one neighbour before trying the next, and it records every vertex it reaches in a visited set. When the search ends, visited holds exactly the vertices connected to source, and the answer is whether destination is among them.

    The visited set does two jobs. Marking a vertex before exploring its neighbours stops the search from walking around a cycle forever, as in the triangle above. And skipping visited vertices means each one is processed once, however many edges lead to it.

    Step by step

    1. Build the adjacency list. Create graph as n empty arrays with Array.from, so index i can hold vertex i's neighbours.
    2. Create the visited set as a Set<number>, which answers "have we been here?" in constant time on average.
    3. Loop over edges with for...of. Each edge is a pair: edge[0] and edge[1] are its endpoints.
    4. Record each edge in both directions. The graph is bi-directional, so push each endpoint onto the other's list. With one direction only, the search would miss paths that run against the listed order.
    5. Start the search with this.dfs(source, graph, visited). The method is in the same class, so it is called through this.
    6. Return visited.has(destination). A Set uses has.
    7. Write dfs. It takes the current node, the graph and the visited set, and returns void.
    8. Mark node as visited first, before looking at its neighbours, so a neighbour that leads back sees it as already visited.
    9. Loop over graph[node] with adjacentNode as the loop variable.
    10. Recurse only on unvisited neighbours, passing the same graph and visited. Without that check, two neighbours would call each other forever.

    The solution in TypeScript

    class Solution {
        validPath(n: number, edges: number[][], source: number, destination: number): boolean {
            const graph: number[][] = Array.from({length: n}, () => []);
            const visited: Set<number> = new Set();
            for (const edge of edges) {
                graph[edge[0]].push(edge[1]);
                graph[edge[1]].push(edge[0]);
            }
            this.dfs(source, graph, visited);
            return visited.has(destination);
        }
    
        dfs(node: number, graph: number[][], visited: Set<number>): void {
            visited.add(node);
            for (const adjacentNode of graph[node]) {
                if (!visited.has(adjacentNode)) {
                    this.dfs(adjacentNode, graph, visited);
                }
            }
        }
    }
    

    TypeScript and JavaScript details:

    • Building the list with Array.from. Array.from({length: n}, () => []) calls the function once per index, so every slot gets its own new empty array. The tempting new Array(n).fill([]) puts the same array in every slot: pushing to graph[0] would then show up in graph[1] and all the rest. For example, new Array(3).fill([]) followed by a single push(1) on the first slot gives [[1],[1],[1]].
    • Annotations are erased. number[][] and Set<number> help the compiler catch mistakes such as pushing a string, but they leave no trace in the compiled JavaScript.
    • Arrays and sets are passed by reference. Every recursive call receives the same graph and the same visited, so what one call adds is visible to all the others. That is why dfs can return void and still deliver its result.
    • Set methods. The membership test is has, not includes (which arrays have). Using includes on a set is a compile error.
    • Methods need this. Writing plain dfs(...) inside the class is a reference to a variable that doesn't exist; the call is this.dfs(...).

    Complexity

    Let V be the number of vertices and E the number of edges.

    Time: O(V + E). Building the adjacency list takes O(V + E). The search visits each vertex once and looks at each edge from both ends, which is 2E checks in total.

    Space: O(V + E). The adjacency list stores 2E entries across V arrays, and the set holds up to V vertices. The recursion adds a call stack as deep as the longest chain of unvisited vertices the search follows.

    That call stack has a limit. The statement allows up to 200,000 vertices, and in a test run, a graph shaped as a single path of 50,000 vertices made this solution throw RangeError: Maximum call stack size exceeded. The depth at which that happens varies, so it is a caution for very deep graphs rather than an exact number. A loop with an explicit stack (an array that you push to and pop from) avoids the limit.

    Edge cases and common mistakes

    • Source equals destination. validPath(1, [], 0, 0) is true: the search marks the source before anything else, and the source is the destination.
    • No edges and different vertices. validPath(2, [], 0, 1) is false.
    • Direction of the listed edge. With edges = [[0,1]], asking for a path from 1 to 0 returns true, because both directions are recorded.
    • Forgetting the second push. The search would only follow edges in the listed direction and miss valid paths.
    • Marking a vertex only after recursing into its neighbours. On a cycle, the calls would never stop.
    • Disconnected groups. The second example shows it: the search from 0 never touches 3, 4 or 5, so they stay out of visited.

    Testing

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