Off-by-one on range boundaries
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Build confidence with an intuition-first walkthrough focused on union-find fundamentals.
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 * 1050 <= edges.length <= 2 * 105edges[i].length == 20 <= ui, vi <= n - 1ui != vi0 <= source, destination <= n - 1Problem summary: 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.
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Union-Find
3 [[0,1],[1,2],[2,0]] 0 2
6 [[0,1],[0,2],[3,5],[5,4],[4,3]] 0 5
valid-arrangement-of-pairs)paths-in-maze-that-lead-to-same-room)Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #1971: Find if Path Exists in Graph
class Solution {
private int destination;
private boolean[] vis;
private List<Integer>[] g;
public boolean validPath(int n, int[][] edges, int source, int destination) {
this.destination = destination;
vis = new boolean[n];
g = new List[n];
Arrays.setAll(g, i -> new ArrayList<>());
for (var e : edges) {
int u = e[0], v = e[1];
g[u].add(v);
g[v].add(u);
}
return dfs(source);
}
private boolean dfs(int i) {
if (i == destination) {
return true;
}
vis[i] = true;
for (var j : g[i]) {
if (!vis[j] && dfs(j)) {
return true;
}
}
return false;
}
}
// Accepted solution for LeetCode #1971: Find if Path Exists in Graph
func validPath(n int, edges [][]int, source int, destination int) bool {
vis := make([]bool, n)
g := make([][]int, n)
for _, e := range edges {
u, v := e[0], e[1]
g[u] = append(g[u], v)
g[v] = append(g[v], u)
}
var dfs func(int) bool
dfs = func(i int) bool {
if i == destination {
return true
}
vis[i] = true
for _, j := range g[i] {
if !vis[j] && dfs(j) {
return true
}
}
return false
}
return dfs(source)
}
# Accepted solution for LeetCode #1971: Find if Path Exists in Graph
class Solution:
def validPath(
self, n: int, edges: List[List[int]], source: int, destination: int
) -> bool:
def dfs(i: int) -> bool:
if i == destination:
return True
if i in vis:
return False
return any(dfs(j) for j in g[i])
g = [[] for _ in range(n)]
for u, v in edges:
g[u].append(v)
g[v].append(u)
vis = set()
return dfs(source)
// Accepted solution for LeetCode #1971: Find if Path Exists in Graph
impl Solution {
pub fn valid_path(n: i32, edges: Vec<Vec<i32>>, source: i32, destination: i32) -> bool {
let n = n as usize;
let source = source as usize;
let destination = destination as usize;
let mut g = vec![Vec::new(); n];
let mut vis = vec![false; n];
for e in edges {
let u = e[0] as usize;
let v = e[1] as usize;
g[u].push(v);
g[v].push(u);
}
fn dfs(g: &Vec<Vec<usize>>, vis: &mut Vec<bool>, i: usize, destination: usize) -> bool {
if i == destination {
return true;
}
vis[i] = true;
for &j in &g[i] {
if !vis[j] && dfs(g, vis, j, destination) {
return true;
}
}
false
}
dfs(&g, &mut vis, source, destination)
}
}
// Accepted solution for LeetCode #1971: Find if Path Exists in Graph
function validPath(n: number, edges: number[][], source: number, destination: number): boolean {
const g: number[][] = Array.from({ length: n }, () => []);
for (const [u, v] of edges) {
g[u].push(v);
g[v].push(u);
}
const vis = new Set<number>();
const dfs = (i: number) => {
if (i === destination) {
return true;
}
if (vis.has(i)) {
return false;
}
vis.add(i);
return g[i].some(dfs);
};
return dfs(source);
}
Use this to step through a reusable interview workflow for this problem.
Track components with a list or adjacency matrix. Each union operation may need to update all n elements’ component labels, giving O(n) per union. For n union operations total: O(n²). Find is O(1) with direct lookup, but union dominates.
With path compression and union by rank, each find/union operation takes O(α(n)) amortized time, where α is the inverse Ackermann function — effectively constant. Space is O(n) for the parent and rank arrays. For m operations on n elements: O(m × α(n)) total.
Review these before coding to avoid predictable interview regressions.
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.