Facebook Pixel

2737. Find the Closest Marked Node 🔒

Problem Description

You are given a directed weighted graph with n nodes (labeled from 0 to n-1) and a list of edges. Each edge is represented as [u, v, w], meaning there's a directed edge from node u to node v with weight w.

You're also given:

  • A starting node s
  • An array marked containing specific nodes of interest

Your task is to find the minimum distance from the starting node s to any of the nodes in the marked array.

The problem asks you to:

  1. Calculate the shortest path from node s to all reachable nodes in the graph
  2. Among all the marked nodes, find which one has the smallest distance from s
  3. Return this minimum distance

If none of the marked nodes can be reached from s, return -1.

Example scenario: If you start at node s and there are multiple marked nodes, you need to find which marked node is closest to s (considering the weighted paths) and return that distance. The graph is directed, so you can only travel along edges in the specified direction.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When we need to find the shortest path from a single source node to multiple destination nodes, we can think about this problem in two ways:

  1. Run a shortest path algorithm from the source to each marked node individually
  2. Run a single shortest path algorithm from the source to all nodes, then check the distances to marked nodes

The second approach is more efficient. Since we're starting from a single source s and need distances to multiple potential destinations, we can compute all shortest paths from s in one go, then simply look up the distances to the marked nodes.

This naturally leads us to think about single-source shortest path algorithms. Dijkstra's algorithm is perfect for this scenario because:

  • It finds the shortest paths from one source to all other nodes
  • It works with weighted graphs (as long as weights are non-negative)
  • We only need to run it once, regardless of how many marked nodes we have

The key insight is that instead of treating each marked node as a separate destination and solving multiple shortest path problems, we solve it once for all nodes. After getting the shortest distances from s to every node in the graph, finding the answer becomes trivial - we just need to check which marked node has the minimum distance.

The algorithm builds an adjacency matrix g[i][j] to store edge weights (using inf for non-existent edges), then applies Dijkstra's algorithm to compute dist[i] - the shortest distance from s to node i. Finally, we scan through all marked nodes and return the minimum distance found, or -1 if all marked nodes are unreachable (distance is inf).

Learn more about Graph, Shortest Path and Heap (Priority Queue) patterns.

Solution Approach

The solution implements Dijkstra's algorithm to find the shortest paths from the starting node s to all other nodes. Let's walk through the implementation step by step:

1. Build the Adjacency Matrix

g = [[inf] * n for _ in range(n)]
for u, v, w in edges:
    g[u][v] = min(g[u][v], w)

We create an n × n matrix g where g[i][j] represents the weight of the edge from node i to node j. Initially, all values are set to inf (infinity). For each edge [u, v, w], we update g[u][v] with the minimum weight (handling potential duplicate edges).

2. Initialize Dijkstra's Algorithm

dist = [inf] * n
vis = [False] * n
dist[s] = 0
  • dist[i] stores the shortest distance from source s to node i
  • vis[i] tracks whether node i has been processed
  • Set the distance to the source itself as 0

3. Main Dijkstra's Loop

for _ in range(n):
    t = -1
    for j in range(n):
        if not vis[j] and (t == -1 or dist[t] > dist[j]):
            t = j
    vis[t] = True
    for j in range(n):
        dist[j] = min(dist[j], dist[t] + g[t][j])

The algorithm runs n iterations:

  • Find the unvisited node with minimum distance: We scan through all nodes to find the unvisited node t with the smallest dist[t] value
  • Mark it as visited: Set vis[t] = True
  • Relax edges: For all neighbors j of node t, we update dist[j] if going through t gives a shorter path: dist[j] = min(dist[j], dist[t] + g[t][j])

4. Find the Answer

ans = min(dist[i] for i in marked)
return -1 if ans >= inf else ans

After computing all shortest distances from s, we check the distances to all marked nodes and return the minimum. If this minimum is still inf (meaning no marked node is reachable), we return -1.

Time Complexity: O(n²) due to the nested loops in Dijkstra's implementation Space Complexity: O(n²) for the adjacency matrix

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example to illustrate the solution approach.

Given:

  • n = 4 nodes (labeled 0, 1, 2, 3)
  • edges = [[0,1,2], [0,2,5], [1,2,1], [1,3,3]]
  • Starting node s = 0
  • Marked nodes marked = [2, 3]

Step 1: Build the Adjacency Matrix

We create a 4×4 matrix initialized with inf, then fill in the edge weights:

     0    1    2    3
0 [ inf,  2,   5,  inf]
1 [ inf, inf,  1,   3 ]
2 [ inf, inf, inf, inf]
3 [ inf, inf, inf, inf]

Step 2: Initialize Dijkstra's Algorithm

  • dist = [0, inf, inf, inf] (distance from node 0 to each node)
  • vis = [False, False, False, False] (no nodes visited yet)

Step 3: Run Dijkstra's Algorithm

Iteration 1:

  • Find unvisited node with minimum distance: node 0 (dist=0)
  • Mark node 0 as visited: vis = [True, False, False, False]
  • Relax edges from node 0:
    • dist[1] = min(inf, 0 + 2) = 2
    • dist[2] = min(inf, 0 + 5) = 5
  • Updated dist = [0, 2, 5, inf]

Iteration 2:

  • Find unvisited node with minimum distance: node 1 (dist=2)
  • Mark node 1 as visited: vis = [True, True, False, False]
  • Relax edges from node 1:
    • dist[2] = min(5, 2 + 1) = 3
    • dist[3] = min(inf, 2 + 3) = 5
  • Updated dist = [0, 2, 3, 5]

Iteration 3:

  • Find unvisited node with minimum distance: node 2 (dist=3)
  • Mark node 2 as visited: vis = [True, True, True, False]
  • Relax edges from node 2: (no outgoing edges)
  • dist remains [0, 2, 3, 5]

Iteration 4:

  • Find unvisited node with minimum distance: node 3 (dist=5)
  • Mark node 3 as visited: vis = [True, True, True, True]
  • Relax edges from node 3: (no outgoing edges)
  • Final dist = [0, 2, 3, 5]

Step 4: Find the Answer

  • Check distances to marked nodes: marked = [2, 3]
    • Distance to node 2: dist[2] = 3
    • Distance to node 3: dist[3] = 5
  • Minimum distance = min(3, 5) = 3
  • Return 3

The shortest path from node 0 to any marked node is 3 (the path 0→1→2).

Solution Implementation

1class Solution:
2    def minimumDistance(
3        self, n: int, edges: List[List[int]], s: int, marked: List[int]
4    ) -> int:
5        """
6        Find the minimum distance from source node s to any marked node.
7      
8        Args:
9            n: Number of nodes in the graph (0 to n-1)
10            edges: List of [from, to, weight] representing directed edges
11            s: Source node
12            marked: List of marked target nodes
13          
14        Returns:
15            Minimum distance to reach any marked node, or -1 if unreachable
16        """
17        # Initialize adjacency matrix with infinity for all pairs
18        # graph[i][j] represents the minimum weight edge from node i to node j
19        graph = [[float('inf')] * n for _ in range(n)]
20      
21        # Build the adjacency matrix from edges
22        # Keep only the minimum weight if there are multiple edges between same nodes
23        for from_node, to_node, weight in edges:
24            graph[from_node][to_node] = min(graph[from_node][to_node], weight)
25      
26        # Initialize distance array for Dijkstra's algorithm
27        # distance[i] represents the shortest distance from source s to node i
28        distance = [float('inf')] * n
29      
30        # Track visited nodes to avoid revisiting
31        visited = [False] * n
32      
33        # Distance from source to itself is 0
34        distance[s] = 0
35      
36        # Dijkstra's algorithm implementation
37        # Process all n nodes
38        for _ in range(n):
39            # Find the unvisited node with minimum distance
40            current_node = -1
41            for node in range(n):
42                if not visited[node] and (current_node == -1 or distance[current_node] > distance[node]):
43                    current_node = node
44          
45            # Mark current node as visited
46            visited[current_node] = True
47          
48            # Update distances to all neighbors of current node
49            for neighbor in range(n):
50                # Relaxation step: check if path through current_node is shorter
51                distance[neighbor] = min(distance[neighbor], distance[current_node] + graph[current_node][neighbor])
52      
53        # Find the minimum distance among all marked nodes
54        min_distance_to_marked = min(distance[node] for node in marked)
55      
56        # Return -1 if no marked node is reachable, otherwise return the minimum distance
57        return -1 if min_distance_to_marked >= float('inf') else min_distance_to_marked
58
1class Solution {
2    public int minimumDistance(int n, List<List<Integer>> edges, int s, int[] marked) {
3        // Define infinity as a large value for unreachable nodes
4        final int INF = 1 << 29;
5      
6        // Initialize adjacency matrix for the graph
7        // graph[i][j] represents the minimum weight of edge from node i to node j
8        int[][] graph = new int[n][n];
9        for (int[] row : graph) {
10            Arrays.fill(row, INF);
11        }
12      
13        // Build the graph from the edge list
14        // Handle multiple edges between same nodes by keeping minimum weight
15        for (List<Integer> edge : edges) {
16            int from = edge.get(0);
17            int to = edge.get(1);
18            int weight = edge.get(2);
19            graph[from][to] = Math.min(graph[from][to], weight);
20        }
21      
22        // Initialize distance array for Dijkstra's algorithm
23        // distance[i] represents the shortest distance from source s to node i
24        int[] distance = new int[n];
25        Arrays.fill(distance, INF);
26        distance[s] = 0;
27      
28        // Track visited nodes during Dijkstra's algorithm
29        boolean[] visited = new boolean[n];
30      
31        // Dijkstra's algorithm implementation
32        // Process all n nodes to find shortest paths
33        for (int i = 0; i < n; i++) {
34            // Find the unvisited node with minimum distance
35            int currentNode = -1;
36            for (int j = 0; j < n; j++) {
37                if (!visited[j] && (currentNode == -1 || distance[currentNode] > distance[j])) {
38                    currentNode = j;
39                }
40            }
41          
42            // Mark current node as visited
43            visited[currentNode] = true;
44          
45            // Update distances to all neighbors of current node
46            for (int neighbor = 0; neighbor < n; neighbor++) {
47                distance[neighbor] = Math.min(distance[neighbor], 
48                                             distance[currentNode] + graph[currentNode][neighbor]);
49            }
50        }
51      
52        // Find the minimum distance among all marked nodes
53        int minDistance = INF;
54        for (int markedNode : marked) {
55            minDistance = Math.min(minDistance, distance[markedNode]);
56        }
57      
58        // Return -1 if no marked node is reachable, otherwise return minimum distance
59        return minDistance >= INF ? -1 : minDistance;
60    }
61}
62
1class Solution {
2public:
3    int minimumDistance(int n, vector<vector<int>>& edges, int s, vector<int>& marked) {
4        // Initialize constants and data structures
5        const int INF = 1 << 29;  // Large value representing infinity
6      
7        // Create adjacency matrix for the graph (initialized with INF)
8        vector<vector<int>> graph(n, vector<int>(n, INF));
9      
10        // Distance array to store shortest distances from source
11        vector<int> distance(n, INF);
12        distance[s] = 0;  // Distance from source to itself is 0
13      
14        // Visited array to track processed nodes
15        vector<bool> visited(n, false);
16      
17        // Build the adjacency matrix from edges
18        // Keep only the minimum weight edge between any two nodes
19        for (auto& edge : edges) {
20            int from = edge[0];
21            int to = edge[1];
22            int weight = edge[2];
23            graph[from][to] = min(graph[from][to], weight);
24        }
25      
26        // Dijkstra's algorithm implementation
27        for (int i = 0; i < n; ++i) {
28            // Find the unvisited node with minimum distance
29            int minNode = -1;
30            for (int j = 0; j < n; ++j) {
31                if (!visited[j] && (minNode == -1 || distance[minNode] > distance[j])) {
32                    minNode = j;
33                }
34            }
35          
36            // Mark the selected node as visited
37            visited[minNode] = true;
38          
39            // Update distances to all neighbors of the selected node
40            for (int neighbor = 0; neighbor < n; ++neighbor) {
41                distance[neighbor] = min(distance[neighbor], 
42                                        distance[minNode] + graph[minNode][neighbor]);
43            }
44        }
45      
46        // Find the minimum distance among all marked nodes
47        int minDistance = INF;
48        for (int markedNode : marked) {
49            minDistance = min(minDistance, distance[markedNode]);
50        }
51      
52        // Return -1 if no marked node is reachable, otherwise return the minimum distance
53        return minDistance >= INF ? -1 : minDistance;
54    }
55};
56
1/**
2 * Finds the minimum distance from source node to any marked node using Dijkstra's algorithm
3 * @param n - Number of nodes in the graph
4 * @param edges - Array of edges where each edge is [from, to, weight]
5 * @param s - Source node index
6 * @param marked - Array of marked node indices
7 * @returns Minimum distance to any marked node, or -1 if unreachable
8 */
9function minimumDistance(n: number, edges: number[][], s: number, marked: number[]): number {
10    // Initialize infinity value for unreachable nodes
11    const INFINITY: number = 1 << 29;
12  
13    // Initialize adjacency matrix with infinity values
14    const adjacencyMatrix: number[][] = Array(n)
15        .fill(0)
16        .map(() => Array(n).fill(INFINITY));
17  
18    // Distance array to store shortest distances from source
19    const distances: number[] = Array(n).fill(INFINITY);
20  
21    // Visited array to track processed nodes
22    const visited: boolean[] = Array(n).fill(false);
23  
24    // Build adjacency matrix from edges, keeping minimum weight for duplicate edges
25    for (const [fromNode, toNode, weight] of edges) {
26        adjacencyMatrix[fromNode][toNode] = Math.min(adjacencyMatrix[fromNode][toNode], weight);
27    }
28  
29    // Set distance to source node as 0
30    distances[s] = 0;
31  
32    // Dijkstra's algorithm main loop - process all nodes
33    for (let i = 0; i < n; ++i) {
34        // Find unvisited node with minimum distance
35        let minDistanceNode: number = -1;
36        for (let j = 0; j < n; ++j) {
37            if (!visited[j] && (minDistanceNode === -1 || distances[minDistanceNode] > distances[j])) {
38                minDistanceNode = j;
39            }
40        }
41      
42        // Mark current node as visited
43        visited[minDistanceNode] = true;
44      
45        // Update distances to all neighbors through current node
46        for (let neighbor = 0; neighbor < n; ++neighbor) {
47            distances[neighbor] = Math.min(
48                distances[neighbor], 
49                distances[minDistanceNode] + adjacencyMatrix[minDistanceNode][neighbor]
50            );
51        }
52    }
53  
54    // Find minimum distance among all marked nodes
55    let minimumMarkedDistance: number = INFINITY;
56    for (const markedNode of marked) {
57        minimumMarkedDistance = Math.min(minimumMarkedDistance, distances[markedNode]);
58    }
59  
60    // Return result: -1 if no marked node is reachable, otherwise the minimum distance
61    return minimumMarkedDistance >= INFINITY ? -1 : minimumMarkedDistance;
62}
63

Time and Space Complexity

Time Complexity: O(n^2)

The algorithm implements Dijkstra's shortest path algorithm using an adjacency matrix representation. The time complexity breaks down as follows:

  • Building the adjacency matrix from edges: O(E) where E is the number of edges
  • The main Dijkstra loop runs n iterations
  • In each iteration:
    • Finding the unvisited node with minimum distance: O(n)
    • Marking the node as visited: O(1)
    • Updating distances to all neighbors: O(n)
  • Total for Dijkstra: O(n) × (O(n) + O(n)) = O(n^2)
  • Finding minimum distance among marked nodes: O(|marked|) which is at most O(n)

Overall time complexity: O(E + n^2 + n) = O(n^2) (since E ≤ n^2 in the worst case)

Space Complexity: O(n^2)

The space usage consists of:

  • Adjacency matrix g: O(n^2) - a 2D array of size n × n
  • Distance array dist: O(n)
  • Visited array vis: O(n)

Overall space complexity: O(n^2 + n + n) = O(n^2)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Handling Multiple Edges Between Same Nodes

One common pitfall is forgetting to handle duplicate edges between the same pair of nodes. The graph might contain multiple edges from node u to node v with different weights.

Pitfall Example:

# Wrong approach - overwrites previous edge
for u, v, w in edges:
    graph[u][v] = w  # This overwrites any existing edge!

Solution: Always take the minimum weight when multiple edges exist:

for u, v, w in edges:
    graph[u][v] = min(graph[u][v], w)

2. Self-Loops and Node Selection in Dijkstra

When the source node is also in the marked array, failing to properly handle the distance to itself (which should be 0) can cause issues.

Pitfall Example: If s = 0 and marked = [0, 2, 3], forgetting to set distance[s] = 0 would result in all distances remaining at infinity.

Solution: Always initialize the source distance before running Dijkstra:

distance[s] = 0  # Critical initialization

3. Integer vs Float Infinity

Using integer maximum values instead of float('inf') can cause overflow issues during addition operations in the relaxation step.

Pitfall Example:

# Wrong - can cause overflow
INF = 10**9
dist[j] = min(dist[j], dist[t] + g[t][j])  # May overflow if both are large

Solution: Use float('inf') which handles arithmetic operations correctly:

graph = [[float('inf')] * n for _ in range(n)]
distance = [float('inf')] * n

4. Incorrect Node Selection Logic

The node selection step in Dijkstra must properly handle the initial case when no node has been selected yet (t = -1).

Pitfall Example:

# Wrong - doesn't handle initial selection correctly
t = 0  # Starting with 0 might select a visited node
for j in range(n):
    if not vis[j] and dist[t] > dist[j]:
        t = j

Solution: Use a sentinel value and check for it:

t = -1
for j in range(n):
    if not vis[j] and (t == -1 or dist[t] > dist[j]):
        t = j

5. Returning Wrong Value for Unreachable Nodes

Forgetting to check if the minimum distance is still infinity before returning can lead to returning infinity instead of -1.

Pitfall Example:

# Wrong - returns infinity instead of -1
return min(distance[i] for i in marked)

Solution: Check for infinity explicitly:

ans = min(distance[i] for i in marked)
return -1 if ans >= float('inf') else ans
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which algorithm should you use to find a node that is close to the root of the tree?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More