Find if Path Exists in Graph
BFS/DFS or Union-Find to check connectivity between two nodes in an undirected graph.
3 min read
union-find bfs graph
Problem#
Given a bi-directional graph with n nodes and edge list edges, return true iff there is a path from source to destination.
Examples#
n = 3, edges = [[0,1],[1,2],[2,0]], source = 0, destination = 2→true.n = 6, edges = [[0,1],[0,2],[3,5],[5,4],[4,3]], source = 0, destination = 5→false.
Constraints#
1 <= n <= 2 * 10^5,0 <= edges.length <= 2 * 10^5.
Hints#
Hint
DSU is the simplest: union edges, then check whether
source and destination share a root. Approach#
Build DSU; union every edge. Return find(source) == find(destination).
Solution#
class Solution { vector<int> parent; int find(int x) { while (parent[x] != x) { parent[x] = parent[parent[x]]; x = parent[x]; } return x; } void unite(int a, int b) { parent[find(a)] = find(b); }public: bool validPath(int n, vector<vector<int>>& edges, int source, int destination) { parent.resize(n); iota(parent.begin(), parent.end(), 0); for (auto& e : edges) unite(e[0], e[1]); return find(source) == find(destination); }};func validPath(n int, edges [][]int, source int, destination int) bool { parent := make([]int, n) for i := range parent { parent[i] = i } var find func(int) int find = func(x int) int { for parent[x] != x { parent[x] = parent[parent[x]] x = parent[x] } return x } unite := func(a, b int) { parent[find(a)] = find(b) } for _, e := range edges { unite(e[0], e[1]) } return find(source) == find(destination)}from typing import List
class Solution: def validPath(self, n: int, edges: List[List[int]], source: int, destination: int) -> bool: parent = list(range(n))
def find(x: int) -> int: while parent[x] != x: parent[x] = parent[parent[x]] x = parent[x] return x
def unite(a: int, b: int) -> None: parent[find(a)] = find(b)
for a, b in edges: unite(a, b) return find(source) == find(destination)function validPath(n, edges, source, destination) { const parent = Array.from({ length: n }, (_, i) => i); const find = (x) => { while (parent[x] !== x) { parent[x] = parent[parent[x]]; x = parent[x]; } return x; }; const unite = (a, b) => { parent[find(a)] = find(b); }; for (const [a, b] of edges) unite(a, b); return find(source) === find(destination);}class Solution { private int[] parent;
public boolean validPath(int n, int[][] edges, int source, int destination) { parent = new int[n]; for (int i = 0; i < n; i++) parent[i] = i; for (int[] e : edges) unite(e[0], e[1]); return find(source) == find(destination); }
private int find(int x) { while (parent[x] != x) { parent[x] = parent[parent[x]]; x = parent[x]; } return x; }
private void unite(int a, int b) { parent[find(a)] = find(b); }}function validPath(n: number, edges: number[][], source: number, destination: number): boolean { const parent: number[] = Array.from({ length: n }, (_, i) => i); const find = (x: number): number => { while (parent[x] !== x) { parent[x] = parent[parent[x]]; x = parent[x]; } return x; }; const unite = (a: number, b: number): void => { parent[find(a)] = find(b); }; for (const [a, b] of edges) unite(a, b); return find(source) === find(destination);}Editorial#
Connectivity is the defining DSU primitive. After unioning all edges, two nodes share a path iff they share a component root. BFS/DFS works equally well but allocates per-query traversal state; DSU answers all “are these connected?” queries in O(α) after a single O(E) build.
Complexity#
- Time: O(N + E * α(N)).
- Space: O(N).
Concept revision#
Related#