2359. Find Closest Node to Given Two Nodes - Explanation
Description
The array edges describes a directed graph with n = edges.length nodes labeled 0 through n - 1. A value edges[i] = j gives an edge from i to j, while -1 means node i has no outgoing edge. Cycles are allowed.
Find a node reachable from both node1 and node2 that minimizes the larger of the two shortest-path distances to it. A distance is the number of edges traveled; a node is distance zero from itself.
Return the chosen node's label. If several nodes achieve the same minimum, return the smallest label. If no node is reachable from both starting nodes, return -1.
Example 1:
Input: edges = [1,2,3,0]
node1 = 0
node2 = 2
Output: 0Explanation: Nodes 0 and 2 both have maximum distance 2 from the two starting nodes. Nodes 1 and 3 have maximum distance 3. The tie between 0 and 2 is resolved by choosing the smaller label, 0.
Example 2:
Input: edges = [1,-1,3,-1]
node1 = 0
node2 = 2
Output: -1Explanation: Node 0 can reach only nodes 0 and 1, while node 2 can reach only nodes 2 and 3. There is no common reachable node.
Example 3:
Input: edges = [2,2,3,-1]
node1 = 1
node2 = 1
Output: 1Explanation: Both starting nodes are node 1. Its distance from itself is 0, which is the smallest possible maximum distance.
Constraints:
n == edges.length2 <= n <= 100000-1 <= edges[i] < nedges[i] != i0 <= node1, node2 < n
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- Graph Representation - Understanding adjacency lists and how to represent directed graphs from edge arrays
- Breadth-First Search (BFS) - Computing shortest distances in unweighted graphs using queue-based traversal
- Depth-First Search (DFS) - Traversing graph paths iteratively or recursively while tracking visited nodes
1. Breadth First Search
Intuition
We want a node reachable from both node1 and node2 that minimizes the maximum of the two distances. First, we compute distances from node1 to all reachable nodes, then do the same from node2. For each node reachable from both, the "cost" is the larger of the two distances. We pick the node with the smallest such cost, breaking ties by choosing the smaller index.
Algorithm
- Build an adjacency list from the edges array.
- Run BFS from
node1to compute distances to all reachable nodes, storing them innode1Dist. - Run BFS from
node2to compute distances, storing them innode2Dist. - Iterate through all nodes. For each node reachable from both sources:
- Compute
dist = max(node1Dist[i], node2Dist[i]). - If this is smaller than the best seen so far, update the result.
- Compute
- Return the best node index, or
-1if none exists.
class Solution:
def closestMeetingNode(self, edges: list[int], node1: int, node2: int) -> int:
adj = defaultdict(list)
for i, nei in enumerate(edges):
adj[i].append(nei)
def bfs(src, distMap):
q = deque([(src, 0)])
distMap[src] = 0
while q:
node, dist = q.popleft()
for nei in adj[node]:
if nei not in distMap:
q.append((nei, dist + 1))
distMap[nei] = dist + 1
node1Dist, node2Dist = {}, {}
bfs(node1, node1Dist)
bfs(node2, node2Dist)
res, resDist = -1, float("inf")
for i in range(len(edges)):
if i in node1Dist and i in node2Dist:
dist = max(node1Dist[i], node2Dist[i])
if dist < resDist:
resDist, res = dist, i
return resTime & Space Complexity
- Time complexity:
- Space complexity:
2. Breadth First Search (Optimal)
Intuition
Since each node has at most one outgoing edge, we do not need a full adjacency list. We can traverse directly using the edges array. The BFS logic remains the same, but we simplify by following edges[node] directly instead of iterating through neighbors.
Algorithm
- Run BFS from
node1: followedges[node]until we hit-1or revisit a node, recording distances innode1Dist. - Run BFS from
node2the same way, storing results innode2Dist. - Scan all nodes. For each one reachable from both sources, track the minimum of
max(node1Dist[i], node2Dist[i]). - Return the node with the smallest maximum distance, or
-1if unreachable.
class Solution:
def closestMeetingNode(self, edges: list[int], node1: int, node2: int) -> int:
n = len(edges)
def bfs(src):
dist = [-1] * n
q = deque([src])
dist[src] = 0
while q:
node = q.popleft()
nei = edges[node]
if nei == -1 or dist[nei] >= 0:
continue
q.append(nei)
dist[nei] = dist[node] + 1
return dist
node1Dist, node2Dist = bfs(node1), bfs(node2)
res, resDist = -1, float("inf")
for i in range(n):
if node1Dist[i] != -1 and node2Dist[i] != -1:
dist = max(node1Dist[i], node2Dist[i])
if dist < resDist:
resDist, res = dist, i
return resTime & Space Complexity
- Time complexity:
- Space complexity:
3. Depth First Search
Intuition
DFS achieves the same goal as BFS here. Starting from each source node, we recursively follow edges and record distances. The graph structure (each node has at most one outgoing edge) means DFS naturally follows the single path from each source without branching.
Algorithm
- Initialize distance arrays
node1Distandnode2Distwith-1(unreachable). - Set
node1Dist[node1] = 0andnode2Dist[node2] = 0. - Run DFS from
node1: for each unvisited neighbor, set its distance and recurse. - Run DFS from
node2similarly. - Find the node with the minimum value of
max(node1Dist[i], node2Dist[i])among nodes reachable from both. - Return that node, or
-1if none.
class Solution:
def closestMeetingNode(self, edges: List[int], node1: int, node2: int) -> int:
n = len(edges)
def dfs(node, dist):
nei = edges[node]
if nei != -1 and dist[nei] == -1:
dist[nei] = dist[node] + 1
dfs(nei, dist)
node1Dist = [-1] * n
node2Dist = [-1] * n
node1Dist[node1] = node2Dist[node2] = 0
dfs(node1, node1Dist)
dfs(node2, node2Dist)
res, resDist = -1, float("inf")
for i in range(n):
if min(node1Dist[i], node2Dist[i]) != -1:
dist = max(node1Dist[i], node2Dist[i])
if dist < resDist:
resDist, res = dist, i
return resTime & Space Complexity
- Time complexity:
- Space complexity:
4. Iterative Depth First Search
Intuition
Since each node has exactly one outgoing edge, we can replace recursion with a simple while loop. Starting from each source, we follow edges iteratively until we reach -1 or a visited node. This avoids recursion overhead while computing the same distances.
Algorithm
- For each source node, initialize its distance to
0. - Iteratively follow
edges[node]while the neighbor exists and is unvisited:- Set the neighbor's distance to current
distance + 1. - Move to the neighbor.
- Set the neighbor's distance to current
- Repeat for both
node1andnode2. - Find the node minimizing
max(node1Dist[i], node2Dist[i])among doubly-reachable nodes. - Return the result.
class Solution:
def closestMeetingNode(self, edges: list[int], node1: int, node2: int) -> int:
n = len(edges)
def dfs(node):
dist = [-1] * n
dist[node] = 0
while edges[node] != -1 and dist[edges[node]] == -1:
nei = edges[node]
dist[nei] = dist[node] + 1
node = nei
return dist
node1Dist, node2Dist = dfs(node1), dfs(node2)
res, resDist = -1, float("inf")
for i in range(n):
if min(node1Dist[i], node2Dist[i]) != -1:
dist = max(node1Dist[i], node2Dist[i])
if dist < resDist:
resDist, res = dist, i
return resTime & Space Complexity
- Time complexity:
- Space complexity:
Common Pitfalls
Using Sum Instead of Maximum Distance
The problem asks for the node that minimizes the maximum of the two distances, not the sum. Using node1Dist[i] + node2Dist[i] instead of max(node1Dist[i], node2Dist[i]) produces incorrect results.
Not Handling Cycles Properly
The graph can contain cycles, so distance computation must mark visited nodes to avoid infinite loops. Always check if a node has already been assigned a distance before processing it.
Forgetting to Return the Smallest Index on Ties
When multiple nodes have the same minimum maximum distance, the problem requires returning the smallest index. Iterating from index 0 and using strict less-than comparison ensures the first valid node is selected.
Sign in to join the discussion