2359. Find Closest Node to Given Two Nodes - Explanation

Problem Link

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:

Find Closest Node to Given Two Nodes, example 1: illustration of the example

Input: edges = [1,2,3,0]
node1 = 0
node2 = 2

Output: 0

Explanation: 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:

Find Closest Node to Given Two Nodes, example 2: illustration of the example

Input: edges = [1,-1,3,-1]
node1 = 0
node2 = 2

Output: -1

Explanation: 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:

Find Closest Node to Given Two Nodes, example 3: illustration of the example

Input: edges = [2,2,3,-1]
node1 = 1
node2 = 1

Output: 1

Explanation: Both starting nodes are node 1. Its distance from itself is 0, which is the smallest possible maximum distance.

Constraints:

  • n == edges.length
  • 2 <= n <= 100000
  • -1 <= edges[i] < n
  • edges[i] != i
  • 0 <= node1, node2 < n


Topics

Company Tags

Please upgrade to NeetCode Pro to view company tags.



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

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

  1. Build an adjacency list from the edges array.
  2. Run BFS from node1 to compute distances to all reachable nodes, storing them in node1Dist.
  3. Run BFS from node2 to compute distances, storing them in node2Dist.
  4. 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.
  5. Return the best node index, or -1 if 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 res

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

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

  1. Run BFS from node1: follow edges[node] until we hit -1 or revisit a node, recording distances in node1Dist.
  2. Run BFS from node2 the same way, storing results in node2Dist.
  3. Scan all nodes. For each one reachable from both sources, track the minimum of max(node1Dist[i], node2Dist[i]).
  4. Return the node with the smallest maximum distance, or -1 if 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 res

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

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

  1. Initialize distance arrays node1Dist and node2Dist with -1 (unreachable).
  2. Set node1Dist[node1] = 0 and node2Dist[node2] = 0.
  3. Run DFS from node1: for each unvisited neighbor, set its distance and recurse.
  4. Run DFS from node2 similarly.
  5. Find the node with the minimum value of max(node1Dist[i], node2Dist[i]) among nodes reachable from both.
  6. Return that node, or -1 if 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 res

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

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

  1. For each source node, initialize its distance to 0.
  2. Iteratively follow edges[node] while the neighbor exists and is unvisited:
    • Set the neighbor's distance to current distance + 1.
    • Move to the neighbor.
  3. Repeat for both node1 and node2.
  4. Find the node minimizing max(node1Dist[i], node2Dist[i]) among doubly-reachable nodes.
  5. 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 res

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

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.