2359. Find Closest Node to Given Two Nodes
Problem Description
You have a directed graph with n
nodes labeled from 0
to n - 1
. Each node can have at most one outgoing edge (meaning each node points to at most one other node).
The graph is represented by an array edges
of size n
where:
edges[i]
indicates there's a directed edge from nodei
to nodeedges[i]
- If node
i
has no outgoing edge, thenedges[i] == -1
Given two specific nodes node1
and node2
, you need to find a node that can be reached from both node1
and node2
. Among all such reachable nodes, you want to select the one that minimizes the maximum distance from either starting node.
More specifically:
- For each node that can be reached from both
node1
andnode2
, calculate the distance fromnode1
to that node and the distance fromnode2
to that node - Take the maximum of these two distances
- Find the node where this maximum distance is as small as possible
If multiple nodes have the same minimal maximum distance, return the one with the smallest index. If no node can be reached from both node1
and node2
, return -1
.
Note that the graph may contain cycles (a path that leads back to a previously visited node).
Example: If node1
can reach node x
with distance 3, and node2
can reach node x
with distance 5, then the maximum distance for node x
would be max(3, 5) = 5
. You want to find the node where this maximum is minimized across all commonly reachable nodes.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: The problem explicitly states we have a directed graph with
n
nodes numbered from0
ton - 1
, where each node has at most one outgoing edge represented by theedges
array.
Is it a tree?
- No: The problem states that the graph may contain cycles, and each node can have at most one outgoing edge (not necessarily forming a tree structure). A tree would have no cycles and exactly
n-1
edges forn
nodes.
Is the problem related to directed acyclic graphs?
- No: The problem explicitly mentions that "edges may contain cycles", so this is not a DAG (Directed Acyclic Graph).
Is the problem related to shortest paths?
- Yes: We need to find distances from
node1
andnode2
to all reachable nodes, then find the node that minimizes the maximum of these two distances. This is fundamentally a shortest path problem.
Is the graph weighted?
- No: The edges don't have weights - each edge has an implicit weight of 1 (we're counting the number of edges as the distance).
BFS
- Yes: Since we have an unweighted graph and need to find shortest paths, BFS is the appropriate choice.
Conclusion: The flowchart suggests using BFS for this problem. While the title mentions DFS pattern, the actual solution uses BFS because:
- We need to find shortest paths in an unweighted graph
- BFS guarantees the shortest path in terms of number of edges
- The solution code confirms this by using a deque and level-by-level traversal (
q = deque([i])
andq.popleft()
)
The BFS approach calculates distances from both starting nodes to all reachable nodes, then finds the common node with the minimum maximum distance.
Intuition
The key insight is that we need to find a "meeting point" that both nodes can reach, and we want this meeting point to be as close as possible to both starting nodes simultaneously.
Think of it like two people starting from different locations trying to meet somewhere. They want to minimize the waiting time for whoever arrives later. The person who has to travel further determines the actual "cost" of that meeting point.
Since each node has at most one outgoing edge, following paths from any node is straightforward - we just keep following the single outgoing edge until we either hit a dead end (edges[i] == -1
) or enter a cycle.
To solve this efficiently, we can:
-
Calculate all distances from both starting points: Use BFS from
node1
to find distances to all reachable nodes. Do the same fromnode2
. BFS is perfect here because we're dealing with an unweighted graph where each edge has the same "cost" of 1. -
Find common reachable nodes: A node is a valid meeting point only if both
node1
andnode2
can reach it. This means the distance from both nodes must be finite (not infinity). -
Minimize the maximum distance: For each valid meeting point, the "cost" is determined by whichever node has to travel further to reach it. We want to minimize this cost across all possible meeting points.
The reason we use max(d1[i], d2[i])
is because both nodes travel simultaneously. If node1 takes 3 steps to reach point X and node2 takes 5 steps, they'll meet when the slower one arrives - after 5 steps. That's why we care about the maximum of the two distances.
Finally, if multiple nodes have the same optimal maximum distance, we choose the one with the smallest index as per the problem requirements.
Learn more about Depth-First Search and Graph patterns.
Solution Approach
The solution implements BFS to calculate distances from both starting nodes, then finds the optimal meeting point by examining all reachable nodes.
Step 1: Build the Graph Representation
g = defaultdict(list)
for i, j in enumerate(edges):
if j != -1:
g[i].append(j)
We convert the edges
array into an adjacency list representation. Each node i
points to node edges[i]
if edges[i] != -1
.
Step 2: BFS Distance Calculation Function
def f(i):
dist = [inf] * n
dist[i] = 0
q = deque([i])
while q:
i = q.popleft()
for j in g[i]:
if dist[j] == inf:
dist[j] = dist[i] + 1
q.append(j)
return dist
This helper function performs BFS from a given starting node:
- Initialize all distances to infinity (
inf
), except the starting node which is 0 - Use a queue to process nodes level by level
- For each unvisited neighbor (distance still
inf
), update its distance and add it to the queue - Return the distance array containing shortest paths from the starting node to all reachable nodes
Step 3: Calculate Distances from Both Nodes
d1 = f(node1) d2 = f(node2)
Run BFS from both node1
and node2
to get their respective distance arrays.
Step 4: Find the Optimal Meeting Point
ans, d = -1, inf
for i, (a, b) in enumerate(zip(d1, d2)):
if (t := max(a, b)) < d:
d = t
ans = i
- Iterate through all nodes using
enumerate(zip(d1, d2))
to get both distances simultaneously - For each node
i
, calculatemax(d1[i], d2[i])
- this is the "cost" of meeting at nodei
- Keep track of the node with the minimum cost
- If a node has distance
inf
from either starting node, it won't be selected (sincemax(a, b)
would beinf
)
The time complexity is O(n)
for building the graph plus O(n)
for each BFS operation, resulting in overall O(n)
complexity. The space complexity is O(n)
for storing the graph and distance arrays.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate the solution approach.
Given:
edges = [2, 2, 3, -1, 3]
node1 = 0
node2 = 1
This creates the following graph:
0 β 2 β 3 (no outgoing edge from 3) 1 β 2 4 β 3
Step 1: Build Graph Representation
g = {0: [2], 1: [2], 2: [3], 4: [3]}
Step 2: BFS from node1 = 0
- Start: distance[0] = 0, queue = [0]
- Process 0: Visit neighbor 2, distance[2] = 1, queue = [2]
- Process 2: Visit neighbor 3, distance[3] = 2, queue = [3]
- Process 3: No neighbors (edges[3] = -1)
- Result:
d1 = [0, inf, 1, 2, inf]
Node 0 can reach: itself (distance 0), node 2 (distance 1), and node 3 (distance 2).
Step 3: BFS from node2 = 1
- Start: distance[1] = 0, queue = [1]
- Process 1: Visit neighbor 2, distance[2] = 1, queue = [2]
- Process 2: Visit neighbor 3, distance[3] = 2, queue = [3]
- Process 3: No neighbors
- Result:
d2 = [inf, 0, 1, 2, inf]
Node 1 can reach: itself (distance 0), node 2 (distance 1), and node 3 (distance 2).
Step 4: Find Optimal Meeting Point Examine each node and calculate max(d1[i], d2[i]):
- Node 0: max(0, inf) = inf (not reachable from node2)
- Node 1: max(inf, 0) = inf (not reachable from node1)
- Node 2: max(1, 1) = 1 β
- Node 3: max(2, 2) = 2 β
- Node 4: max(inf, inf) = inf (not reachable from either)
Common reachable nodes are 2 and 3 with maximum distances of 1 and 2 respectively.
Answer: Node 2 (minimum maximum distance = 1)
This demonstrates why we use the maximum distance: at node 2, both starting nodes need 1 step to meet, so they arrive simultaneously. At node 3, both need 2 steps. Node 2 is optimal because it minimizes the travel time for the slower traveler.
Solution Implementation
1from collections import defaultdict, deque
2from typing import List
3
4class Solution:
5 def closestMeetingNode(self, edges: List[int], node1: int, node2: int) -> int:
6 """
7 Find the node that minimizes the maximum distance from both node1 and node2.
8
9 Args:
10 edges: List where edges[i] represents the outgoing edge from node i (-1 if no edge)
11 node1: Starting node 1
12 node2: Starting node 2
13
14 Returns:
15 The index of the meeting node, or -1 if no common reachable node exists
16 """
17
18 def compute_distances_from_node(start_node: int) -> List[float]:
19 """
20 Compute shortest distances from start_node to all other nodes using BFS.
21
22 Args:
23 start_node: The node to start BFS from
24
25 Returns:
26 List of distances where distances[i] is the distance from start_node to node i
27 """
28 # Initialize all distances as infinity
29 distances = [float('inf')] * num_nodes
30 distances[start_node] = 0
31
32 # BFS queue initialized with the starting node
33 queue = deque([start_node])
34
35 while queue:
36 current_node = queue.popleft()
37
38 # Explore neighbors of current node
39 for neighbor in adjacency_list[current_node]:
40 # If neighbor hasn't been visited yet
41 if distances[neighbor] == float('inf'):
42 distances[neighbor] = distances[current_node] + 1
43 queue.append(neighbor)
44
45 return distances
46
47 # Build adjacency list from edges array
48 adjacency_list = defaultdict(list)
49 for source_node, target_node in enumerate(edges):
50 if target_node != -1: # -1 indicates no outgoing edge
51 adjacency_list[source_node].append(target_node)
52
53 # Total number of nodes in the graph
54 num_nodes = len(edges)
55
56 # Compute distances from both starting nodes
57 distances_from_node1 = compute_distances_from_node(node1)
58 distances_from_node2 = compute_distances_from_node(node2)
59
60 # Find the best meeting node
61 best_meeting_node = -1
62 min_max_distance = float('inf')
63
64 # Check each node as a potential meeting point
65 for node_index, (dist_from_node1, dist_from_node2) in enumerate(zip(distances_from_node1, distances_from_node2)):
66 # Calculate the maximum distance for this meeting node
67 max_distance_to_node = max(dist_from_node1, dist_from_node2)
68
69 # Update the best meeting node if this one is better
70 if max_distance_to_node < min_max_distance:
71 min_max_distance = max_distance_to_node
72 best_meeting_node = node_index
73
74 return best_meeting_node
75
1class Solution {
2 private int numberOfNodes;
3 private List<Integer>[] adjacencyList;
4
5 /**
6 * Finds the node that can be reached by both node1 and node2 with the minimum maximum distance.
7 * @param edges Array where edges[i] represents the outgoing edge from node i (-1 if no edge)
8 * @param node1 Starting node 1
9 * @param node2 Starting node 2
10 * @return The node index with minimum maximum distance, or -1 if no common reachable node exists
11 */
12 public int closestMeetingNode(int[] edges, int node1, int node2) {
13 numberOfNodes = edges.length;
14
15 // Build adjacency list representation of the graph
16 adjacencyList = new List[numberOfNodes];
17 Arrays.setAll(adjacencyList, index -> new ArrayList<>());
18
19 // Populate adjacency list based on edges array
20 for (int i = 0; i < numberOfNodes; i++) {
21 if (edges[i] != -1) {
22 adjacencyList[i].add(edges[i]);
23 }
24 }
25
26 // Calculate distances from node1 to all other nodes
27 int[] distancesFromNode1 = calculateDistances(node1);
28
29 // Calculate distances from node2 to all other nodes
30 int[] distancesFromNode2 = calculateDistances(node2);
31
32 // Find the node with minimum maximum distance from both starting nodes
33 int minMaxDistance = 1 << 30; // Initialize with a large value (2^30)
34 int resultNode = -1;
35
36 for (int i = 0; i < numberOfNodes; i++) {
37 // Get the maximum distance to node i from either starting node
38 int maxDistanceToCurrentNode = Math.max(distancesFromNode1[i], distancesFromNode2[i]);
39
40 // Update result if we found a better (smaller) maximum distance
41 if (maxDistanceToCurrentNode < minMaxDistance) {
42 minMaxDistance = maxDistanceToCurrentNode;
43 resultNode = i;
44 }
45 }
46
47 return resultNode;
48 }
49
50 /**
51 * Performs BFS to calculate shortest distances from a starting node to all other nodes.
52 * @param startNode The starting node for BFS
53 * @return Array of distances where distances[i] is the shortest distance from startNode to node i
54 */
55 private int[] calculateDistances(int startNode) {
56 // Initialize distances array with large values (unreachable)
57 int[] distances = new int[numberOfNodes];
58 Arrays.fill(distances, 1 << 30); // Use 2^30 as infinity
59
60 // Distance to starting node is 0
61 distances[startNode] = 0;
62
63 // BFS queue
64 Deque<Integer> queue = new ArrayDeque<>();
65 queue.offer(startNode);
66
67 // Perform BFS
68 while (!queue.isEmpty()) {
69 int currentNode = queue.poll();
70
71 // Explore all neighbors of current node
72 for (int neighbor : adjacencyList[currentNode]) {
73 // If neighbor hasn't been visited yet
74 if (distances[neighbor] == 1 << 30) {
75 distances[neighbor] = distances[currentNode] + 1;
76 queue.offer(neighbor);
77 }
78 }
79 }
80
81 return distances;
82 }
83}
84
1class Solution {
2public:
3 int closestMeetingNode(vector<int>& edges, int node1, int node2) {
4 int n = edges.size();
5
6 // Build adjacency list representation of the graph
7 // Each node has at most one outgoing edge
8 vector<vector<int>> graph(n);
9 for (int i = 0; i < n; ++i) {
10 if (edges[i] != -1) {
11 graph[i].push_back(edges[i]);
12 }
13 }
14
15 const int INF = 1 << 30; // Large value representing infinity
16
17 // Lambda function to compute shortest distances from a starting node using BFS
18 auto computeDistances = [&](int startNode) -> vector<int> {
19 vector<int> distances(n, INF);
20 distances[startNode] = 0;
21
22 // BFS to find shortest paths
23 queue<int> bfsQueue;
24 bfsQueue.push(startNode);
25
26 while (!bfsQueue.empty()) {
27 int currentNode = bfsQueue.front();
28 bfsQueue.pop();
29
30 // Explore neighbors (at most one neighbor due to graph structure)
31 for (int neighbor : graph[currentNode]) {
32 if (distances[neighbor] == INF) { // Not visited yet
33 distances[neighbor] = distances[currentNode] + 1;
34 bfsQueue.push(neighbor);
35 }
36 }
37 }
38
39 return distances;
40 };
41
42 // Calculate distances from node1 and node2 to all other nodes
43 vector<int> distancesFromNode1 = computeDistances(node1);
44 vector<int> distancesFromNode2 = computeDistances(node2);
45
46 // Find the node with minimum maximum distance from both starting nodes
47 int resultNode = -1;
48 int minMaxDistance = INF;
49
50 for (int i = 0; i < n; ++i) {
51 // Maximum distance from either node1 or node2 to node i
52 int maxDistanceToNode = max(distancesFromNode1[i], distancesFromNode2[i]);
53
54 // Update result if we found a better meeting node
55 if (maxDistanceToNode < minMaxDistance) {
56 minMaxDistance = maxDistanceToNode;
57 resultNode = i;
58 }
59 }
60
61 return resultNode;
62 }
63};
64
1/**
2 * Finds the node that can be reached from both node1 and node2 with minimum maximum distance
3 * @param edges - Array where edges[i] represents the outgoing edge from node i (-1 if no edge)
4 * @param node1 - Starting node 1
5 * @param node2 - Starting node 2
6 * @returns The index of the meeting node with minimum maximum distance, or -1 if no common node exists
7 */
8function closestMeetingNode(edges: number[], node1: number, node2: number): number {
9 const nodeCount = edges.length;
10
11 // Build adjacency list representation of the graph
12 const adjacencyList: number[][] = Array.from({ length: nodeCount }, () => []);
13 for (let i = 0; i < nodeCount; ++i) {
14 if (edges[i] !== -1) {
15 adjacencyList[i].push(edges[i]);
16 }
17 }
18
19 const INFINITY = 1 << 30;
20
21 /**
22 * Performs BFS from a starting node to calculate distances to all other nodes
23 * @param startNode - The node to start BFS from
24 * @returns Array of distances from startNode to all other nodes
25 */
26 const calculateDistances = (startNode: number): number[] => {
27 const distances: number[] = new Array(nodeCount).fill(INFINITY);
28 distances[startNode] = 0;
29
30 const queue: number[] = [startNode];
31
32 while (queue.length > 0) {
33 const currentNode = queue.shift()!;
34
35 // Explore all neighbors of current node
36 for (const neighbor of adjacencyList[currentNode]) {
37 if (distances[neighbor] === INFINITY) {
38 distances[neighbor] = distances[currentNode] + 1;
39 queue.push(neighbor);
40 }
41 }
42 }
43
44 return distances;
45 };
46
47 // Calculate distances from both starting nodes
48 const distancesFromNode1 = calculateDistances(node1);
49 const distancesFromNode2 = calculateDistances(node2);
50
51 // Find the node with minimum maximum distance from both starting nodes
52 let resultNode = -1;
53 let minimumMaxDistance = INFINITY;
54
55 for (let i = 0; i < nodeCount; ++i) {
56 const maxDistanceToNode = Math.max(distancesFromNode1[i], distancesFromNode2[i]);
57
58 if (maxDistanceToNode < minimumMaxDistance) {
59 minimumMaxDistance = maxDistanceToNode;
60 resultNode = i;
61 }
62 }
63
64 return resultNode;
65}
66
Time and Space Complexity
Time Complexity: O(n)
The algorithm consists of the following operations:
- Building the graph from edges:
O(n)
wheren
is the number of nodes - Running BFS from
node1
:O(n)
- each node is visited at most once, and each edge is traversed at most once. Since this is a directed graph where each node has at most one outgoing edge, there are at mostn
edges total - Running BFS from
node2
:O(n)
- same reasoning as above - Finding the minimum maximum distance:
O(n)
- iterating through all nodes once
Total time complexity: O(n) + O(n) + O(n) + O(n) = O(n)
Space Complexity: O(n)
The space usage includes:
- Graph representation
g
:O(n)
- storing at mostn
edges - Distance array
d1
:O(n)
- Distance array
d2
:O(n)
- BFS queue:
O(n)
in the worst case when all nodes are reachable - Other variables:
O(1)
Total space complexity: O(n) + O(n) + O(n) + O(n) = O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Not Handling Cycles Properly in Distance Calculation
The Pitfall: A common mistake is using DFS or a simple traversal without tracking visited nodes, which can lead to infinite loops when the graph contains cycles. Since each node has at most one outgoing edge, cycles are very likely to occur.
Why It Happens: Consider a graph where node 0 β 1 β 2 β 0 (forming a cycle). Without proper visited tracking, the traversal would loop infinitely: 0 β 1 β 2 β 0 β 1 β 2 β ...
The Solution:
The BFS approach with visited tracking (using distances[neighbor] == float('inf')
as the check) naturally handles cycles:
if distances[neighbor] == float('inf'): # Only process unvisited nodes
distances[neighbor] = distances[current_node] + 1
queue.append(neighbor)
Once a node is visited (distance is no longer infinity), it won't be added to the queue again, preventing infinite loops.
2. Incorrectly Identifying Unreachable Nodes
The Pitfall:
Forgetting to check if a node is reachable from both starting nodes before considering it as a valid meeting point. Simply checking max(dist1, dist2)
without verifying both distances are finite can lead to incorrect results.
Why It Happens:
If node X is reachable from node1 (distance = 3) but not from node2 (distance = inf), then max(3, inf) = inf
. Without proper handling, this might still be processed, potentially causing the algorithm to return -1 even when valid meeting nodes exist.
The Solution: The current implementation handles this correctly because:
max_distance_to_node = max(dist_from_node1, dist_from_node2)
if max_distance_to_node < min_max_distance: # inf won't be less than any finite number
Any node unreachable from either start point will have max_distance = inf
, which will never be less than a finite min_max_distance
, effectively filtering out invalid nodes.
3. Using DFS Instead of BFS for Shortest Path
The Pitfall: Using DFS to find distances in this graph structure, which doesn't guarantee shortest paths.
Why It Happens: DFS explores one path completely before backtracking. In a graph with cycles or multiple paths, DFS might find a longer path to a node before finding the shortest one.
Example:
edges = [1, 2, 3, 1, -1] # Graph: 0β1β2β3β1 (cycle between 1,2,3)
DFS from node 0 might reach node 3 via path 0β1β2β3 (distance 3), while BFS correctly identifies it can be reached in distance 3.
The Solution: Always use BFS for unweighted shortest path problems:
queue = deque([start_node]) while queue: current_node = queue.popleft() # Process nodes level by level
BFS explores all nodes at distance d before exploring nodes at distance d+1, guaranteeing shortest paths.
Which of the two traversal algorithms (BFS and DFS) can be used to find whether two nodes are connected?
Recommended Readings
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
https assets algo monster cover_photos graph svg Graph Fundamentals Tree with 0 cycle At this point you should be pretty familiar with trees A tree is a special kind of graph a connected acyclic cycle less graph A graph may contain cycle s and nodes could be disconnected A tree
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Donβt Miss This!