3243. Shortest Distance After Road Addition Queries I
Problem Description
You have n
cities numbered from 0
to n - 1
. Initially, these cities are connected by one-way roads where each city i
has a road going to city i + 1
(for all 0 <= i < n - 1
). This forms a simple path from city 0
to city n - 1
.
You're given a list of queries where each query queries[i] = [u_i, v_i]
represents adding a new one-way road from city u_i
to city v_i
. These new roads act as shortcuts that can potentially reduce the travel distance between cities.
Your task is to process these queries one by one. After adding each new road, you need to find the shortest path from city 0
to city n - 1
. The result should be an array where answer[i]
represents the length of the shortest path from city 0
to city n - 1
after processing the first i + 1
queries.
For example, if n = 5
, initially you have roads: 0β1β2β3β4
, giving a shortest path length of 4. If the first query adds a road from city 0
to city 3
, then the new shortest path would be 0β3β4
with length 2.
The key points to understand:
- Roads are unidirectional (one-way only)
- Initially, there's a sequential path from city
0
to cityn - 1
- Each query adds a new road that might create a shortcut
- You need to calculate the shortest path after each query is processed
- The path length is the number of roads traveled, not the number of cities visited
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 involves cities (nodes) connected by roads (edges). We have a directed graph where initially each city
i
connects to cityi+1
, and we're adding new edges with each query.
Is it a tree?
- No: The graph is not a tree because after adding new roads from queries, we can have multiple paths between cities and potentially create cycles.
Is the problem related to directed acyclic graphs?
- No: While the graph is directed, it's not specifically about DAG properties like topological ordering.
Is the problem related to shortest paths?
- Yes: We need to find the shortest path from city
0
to cityn-1
after each query.
Is the graph weighted?
- No: All roads have the same weight (cost of 1). We're counting the number of roads traveled, not summing weights.
BFS
- Yes: Since we have an unweighted graph and need to find shortest paths, BFS is the appropriate algorithm.
Conclusion: The flowchart correctly leads us to use BFS (Breadth-First Search) for finding the shortest path in an unweighted directed graph. BFS guarantees finding the shortest path in terms of the number of edges when all edges have equal weight.
Intuition
When we think about this problem, we start with a simple chain of cities connected sequentially: 0β1β2β...β(n-1)
. Each query adds a new "shortcut" road that might help us reach the destination faster.
The key insight is that after adding each new road, we need to recalculate the shortest path because this new road might create a better route. Since all roads have the same "cost" (each road counts as 1 in the path length), this is an unweighted graph problem.
Why BFS? In an unweighted graph, BFS naturally finds the shortest path by exploring nodes level by level. When BFS first reaches a node, it's guaranteed to have found the shortest path to that node. This property makes BFS perfect for our problem.
Here's the thought process:
- We maintain an adjacency list
g
whereg[i]
stores all cities directly reachable from cityi
- Initially,
g[i] = [i+1]
for all cities except the last one - For each query
[u, v]
, we addv
tog[u]
, creating a new road - After adding each road, we run BFS from city
0
to find the shortest distance to cityn-1
The BFS works by:
- Starting from city
0
with distance 0 - Exploring all cities at distance 1, then distance 2, and so on
- When we first reach city
n-1
, we've found the shortest path
This approach is straightforward because we don't need to optimize for weighted edges or worry about negative cycles - we simply need to count the minimum number of "hops" from start to end after each road addition.
Learn more about Breadth-First Search and Graph patterns.
Solution Approach
The implementation follows a straightforward BFS approach with dynamic graph updates:
Graph Representation:
We use an adjacency list g
where g[i]
is a list containing all cities directly reachable from city i
. Initially, we build this graph with:
g = [[i + 1] for i in range(n - 1)]
This creates the initial roads where city i
connects to city i + 1
.
Processing Queries:
For each query [u, v]
, we:
- Add the new road by appending
v
tog[u]
- Run BFS to find the new shortest distance from city
0
to cityn-1
- Store this distance in our answer array
BFS Implementation: The BFS function works as follows:
-
Initialization: Start with city
0
in the queue, mark it as visited, and set distanced = 0
-
Level-by-level exploration: Process all nodes at the current distance level before moving to the next level
for _ in range(len(q)): u = q.popleft()
-
Target check: When we dequeue city
n-1
, we immediately return the current distanced
as it's guaranteed to be the shortest -
Neighbor exploration: For each unvisited neighbor
v
of the current cityu
:- Mark
v
as visited - Add
v
to the queue for the next level
- Mark
-
Distance increment: After processing all nodes at the current level, increment
d
by 1
The use of a vis
array prevents revisiting cities and ensures we find the shortest path. Since BFS explores nodes level by level, the first time we reach the destination is guaranteed to be via the shortest path.
Time Complexity: For each query, BFS takes O(n + edges)
time. With q
queries, the total time is O(q Γ (n + edges))
.
Space Complexity: O(n)
for the graph representation and BFS data structures.
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 small example with n = 5
cities and queries = [[0, 3], [2, 4]]
.
Initial Setup:
- Cities: 0, 1, 2, 3, 4
- Initial roads: 0β1, 1β2, 2β3, 3β4
- Graph representation:
g = [[1], [2], [3], [4], []]
- Initial shortest path: 0β1β2β3β4 (length = 4)
Processing Query 1: [0, 3]
- Add road from city 0 to city 3:
g[0].append(3)
, sog = [[1, 3], [2], [3], [4], []]
- Run BFS from city 0:
- Start: queue = [0], visited = {0}, distance = 0
- Level 0: Process city 0
- Neighbors: 1 and 3 (both unvisited)
- Add to queue: [1, 3], visited = {0, 1, 3}
- Level 1: Process cities 1 and 3
- From city 1: neighbor 2 (unvisited) β queue = [3, 2]
- From city 3: neighbor 4 (unvisited) β queue = [2, 4]
- Combined queue after level: [2, 4], visited = {0, 1, 2, 3, 4}
- Level 2: Process cities 2 and 4
- City 4 is our target (n-1)! Return distance = 2
- New shortest path: 0β3β4 (length = 2)
- Answer so far: [2]
Processing Query 2: [2, 4]
- Add road from city 2 to city 4:
g[2].append(4)
, sog = [[1, 3], [2], [3, 4], [4], []]
- Run BFS from city 0:
- Start: queue = [0], visited = {0}, distance = 0
- Level 0: Process city 0
- Neighbors: 1 and 3 β queue = [1, 3], visited = {0, 1, 3}
- Level 1: Process cities 1 and 3
- From city 1: neighbor 2 β queue = [3, 2]
- From city 3: neighbor 4 β queue = [2, 4]
- Combined queue after level: [2, 4], visited = {0, 1, 2, 3, 4}
- Level 2: Process cities 2 and 4
- City 4 is our target! Return distance = 2
- Shortest path remains: 0β3β4 (length = 2)
- Note: The new road 2β4 doesn't improve our shortest path
- Final answer: [2, 2]
Key Observations:
- The first query created a significant shortcut, reducing the path from 4 to 2
- The second query didn't improve the shortest path since we already had a 2-hop route
- BFS efficiently finds the shortest path by exploring all cities at distance d before moving to distance d+1
- When BFS first encounters city n-1, we're guaranteed to have found the shortest path
Solution Implementation
1from collections import deque
2from typing import List
3
4class Solution:
5 def shortestDistanceAfterQueries(
6 self, n: int, queries: List[List[int]]
7 ) -> List[int]:
8 """
9 Find shortest distances from node 0 to node n-1 after each query.
10 Each query adds a new directed edge to the graph.
11
12 Args:
13 n: Number of nodes in the graph (0 to n-1)
14 queries: List of [u, v] pairs representing edges to add
15
16 Returns:
17 List of shortest distances after each query
18 """
19
20 def bfs(start: int) -> int:
21 """
22 Perform BFS to find shortest distance from start node to node n-1.
23
24 Args:
25 start: Starting node for BFS
26
27 Returns:
28 Shortest distance from start to node n-1
29 """
30 # Initialize queue with starting node
31 queue = deque([start])
32
33 # Track visited nodes to avoid cycles
34 visited = [False] * n
35 visited[start] = True
36
37 # Distance from start node
38 distance = 0
39
40 # BFS traversal
41 while True:
42 # Process all nodes at current distance level
43 for _ in range(len(queue)):
44 current_node = queue.popleft()
45
46 # Check if we reached the target node (n-1)
47 if current_node == n - 1:
48 return distance
49
50 # Add unvisited neighbors to queue
51 for neighbor in adjacency_list[current_node]:
52 if not visited[neighbor]:
53 visited[neighbor] = True
54 queue.append(neighbor)
55
56 # Increment distance for next level
57 distance += 1
58
59 # Initialize adjacency list with default edges (i -> i+1)
60 # Each node initially connects to the next node in sequence
61 adjacency_list = [[i + 1] for i in range(n - 1)]
62
63 # Store results after each query
64 result = []
65
66 # Process each query
67 for source, destination in queries:
68 # Add new edge to the graph
69 adjacency_list[source].append(destination)
70
71 # Calculate shortest distance after adding this edge
72 shortest_distance = bfs(0)
73 result.append(shortest_distance)
74
75 return result
76
1class Solution {
2 // Adjacency list representation of the graph
3 private List<Integer>[] adjacencyList;
4 // Number of nodes in the graph
5 private int nodeCount;
6
7 /**
8 * Finds shortest distances from node 0 to node n-1 after each query.
9 * Initially, the graph has edges from i to i+1 for all 0 <= i < n-1.
10 * Each query adds a new edge from queries[i][0] to queries[i][1].
11 *
12 * @param n The number of nodes in the graph (0 to n-1)
13 * @param queries Array of edges to be added, where queries[i] = [u, v] adds edge u -> v
14 * @return Array of shortest distances from node 0 to node n-1 after each query
15 */
16 public int[] shortestDistanceAfterQueries(int n, int[][] queries) {
17 this.nodeCount = n;
18
19 // Initialize adjacency list for each node
20 adjacencyList = new List[n];
21 Arrays.setAll(adjacencyList, index -> new ArrayList<>());
22
23 // Create initial edges: 0->1, 1->2, ..., (n-2)->(n-1)
24 for (int i = 0; i < n - 1; i++) {
25 adjacencyList[i].add(i + 1);
26 }
27
28 int queryCount = queries.length;
29 int[] result = new int[queryCount];
30
31 // Process each query by adding the new edge and finding shortest path
32 for (int i = 0; i < queryCount; i++) {
33 int sourceNode = queries[i][0];
34 int destinationNode = queries[i][1];
35
36 // Add the new edge to the graph
37 adjacencyList[sourceNode].add(destinationNode);
38
39 // Find shortest path from node 0 to node n-1 using BFS
40 result[i] = findShortestPath(0);
41 }
42
43 return result;
44 }
45
46 /**
47 * Performs BFS to find the shortest path from start node to the last node (n-1).
48 *
49 * @param startNode The starting node for BFS (typically 0)
50 * @return The shortest distance from startNode to node n-1
51 */
52 private int findShortestPath(int startNode) {
53 // Queue for BFS traversal
54 Deque<Integer> queue = new ArrayDeque<>();
55 queue.offer(startNode);
56
57 // Track visited nodes to avoid cycles
58 boolean[] visited = new boolean[nodeCount];
59 visited[startNode] = true;
60
61 // BFS level by level to find shortest path
62 for (int distance = 0; ; distance++) {
63 // Process all nodes at current distance level
64 int nodesAtCurrentLevel = queue.size();
65
66 for (int i = 0; i < nodesAtCurrentLevel; i++) {
67 int currentNode = queue.poll();
68
69 // Check if we've reached the destination
70 if (currentNode == nodeCount - 1) {
71 return distance;
72 }
73
74 // Explore all neighbors of current node
75 for (int neighbor : adjacencyList[currentNode]) {
76 if (!visited[neighbor]) {
77 visited[neighbor] = true;
78 queue.offer(neighbor);
79 }
80 }
81 }
82 }
83 }
84}
85
1class Solution {
2public:
3 vector<int> shortestDistanceAfterQueries(int n, vector<vector<int>>& queries) {
4 // Initialize adjacency list for the graph
5 // Initially, each city i has a road to city i+1
6 vector<vector<int>> graph(n);
7 for (int i = 0; i < n - 1; ++i) {
8 graph[i].push_back(i + 1);
9 }
10
11 // BFS function to find shortest distance from start to city n-1
12 auto bfs = [&](int start) -> int {
13 queue<int> bfsQueue;
14 bfsQueue.push(start);
15
16 // Track visited nodes to avoid cycles
17 vector<bool> visited(n, false);
18 visited[start] = true;
19
20 // Level-order BFS to track distance
21 int distance = 0;
22 while (true) {
23 // Process all nodes at current distance level
24 int levelSize = bfsQueue.size();
25 for (int i = 0; i < levelSize; ++i) {
26 int currentNode = bfsQueue.front();
27 bfsQueue.pop();
28
29 // Check if we reached the destination
30 if (currentNode == n - 1) {
31 return distance;
32 }
33
34 // Explore all neighbors
35 for (int neighbor : graph[currentNode]) {
36 if (!visited[neighbor]) {
37 visited[neighbor] = true;
38 bfsQueue.push(neighbor);
39 }
40 }
41 }
42 // Move to next distance level
43 distance++;
44 }
45 };
46
47 // Process each query and compute shortest distance after adding new road
48 vector<int> result;
49 for (const auto& query : queries) {
50 // Add new road from query[0] to query[1]
51 int from = query[0];
52 int to = query[1];
53 graph[from].push_back(to);
54
55 // Calculate shortest distance from city 0 to city n-1
56 result.push_back(bfs(0));
57 }
58
59 return result;
60 }
61};
62
1/**
2 * Finds the shortest distance from node 0 to node n-1 after each query.
3 * Each query adds a new edge to the graph.
4 *
5 * @param n - The number of nodes in the graph (0 to n-1)
6 * @param queries - Array of queries, each query [u, v] adds an edge from u to v
7 * @returns Array of shortest distances after each query
8 */
9function shortestDistanceAfterQueries(n: number, queries: number[][]): number[] {
10 // Initialize adjacency list for the graph
11 // Initially, each node i has an edge to node i+1 (except the last node)
12 const adjacencyList: number[][] = Array.from({ length: n }, () => []);
13
14 // Create initial edges: 0->1, 1->2, ..., (n-2)->(n-1)
15 for (let i = 0; i < n - 1; i++) {
16 adjacencyList[i].push(i + 1);
17 }
18
19 /**
20 * Performs BFS to find the shortest distance from a starting node to node n-1
21 *
22 * @param startNode - The starting node for BFS
23 * @returns The shortest distance to node n-1
24 */
25 const bfs = (startNode: number): number => {
26 // Initialize queue with the starting node
27 let queue: number[] = [startNode];
28
29 // Track visited nodes to avoid cycles
30 const visited: boolean[] = Array(n).fill(false);
31 visited[startNode] = true;
32
33 // BFS level by level
34 for (let distance = 0; ; distance++) {
35 const nextLevelQueue: number[] = [];
36
37 // Process all nodes at the current level
38 for (const currentNode of queue) {
39 // Check if we've reached the destination
40 if (currentNode === n - 1) {
41 return distance;
42 }
43
44 // Explore all neighbors of the current node
45 for (const neighbor of adjacencyList[currentNode]) {
46 if (!visited[neighbor]) {
47 visited[neighbor] = true;
48 nextLevelQueue.push(neighbor);
49 }
50 }
51 }
52
53 // Move to the next level
54 queue = nextLevelQueue;
55 }
56 };
57
58 // Store results for each query
59 const results: number[] = [];
60
61 // Process each query
62 for (const [fromNode, toNode] of queries) {
63 // Add the new edge to the graph
64 adjacencyList[fromNode].push(toNode);
65
66 // Calculate and store the shortest distance after adding this edge
67 results.push(bfs(0));
68 }
69
70 return results;
71}
72
Time and Space Complexity
Time Complexity: O(q Γ (n + q))
For each query, we perform a BFS traversal:
- BFS in the worst case visits all
n
nodes - After processing
q
queries, each node can have at mostq + 1
outgoing edges (1 original edge plus up toq
added edges) - Thus, the total number of edges after
q
queries is at mostn - 1 + q
- Each BFS takes
O(V + E) = O(n + (n - 1 + q)) = O(n + q)
time - Since we run BFS for each of the
q
queries, the total time complexity isO(q Γ (n + q))
Space Complexity: O(n + q)
The space usage consists of:
- Graph adjacency list
g
: Initially hasn - 1
lists with 1 element each. Afterq
queries, it can have up ton - 1 + q
total edges stored, requiringO(n + q)
space - BFS queue
q
: Can contain at mostn
nodes at any time, requiringO(n)
space - Visited array
vis
: Always has exactlyn
boolean values, requiringO(n)
space - Result array
ans
: Storesq
results, requiringO(q)
space
The overall space complexity is O(n + q)
as the graph storage dominates after multiple queries are added.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Graph Initialization
A frequent mistake is incorrectly initializing the adjacency list, especially forgetting that city n-1
has no outgoing edges initially.
Incorrect:
# This creates n lists instead of n-1
adjacency_list = [[i + 1] for i in range(n)] # Wrong! Index n-1 would point to n
Correct:
# City n-1 has no outgoing edges initially
adjacency_list = [[i + 1] for i in range(n - 1)]
2. BFS Early Termination Logic
The current implementation checks if we've reached the destination after dequeuing, which is correct. A common mistake is checking before adding to the queue, which can lead to incorrect distance calculations.
Incorrect:
for neighbor in adjacency_list[current_node]: if neighbor == n - 1: return distance + 1 # Wrong! This might not be the shortest path if not visited[neighbor]: visited[neighbor] = True queue.append(neighbor)
Correct:
# Check after dequeuing to ensure we're processing at the correct distance level if current_node == n - 1: return distance
3. Handling Edge Cases
The code assumes we'll always find a path (which is valid given the problem constraints). However, a more robust implementation should handle the case where no path exists or when n <= 1
.
Enhanced Solution:
def bfs(start: int) -> int:
if n <= 1:
return 0
if start == n - 1:
return 0
queue = deque([start])
visited = [False] * n
visited[start] = True
distance = 0
while queue: # Changed from while True
for _ in range(len(queue)):
current_node = queue.popleft()
if current_node == n - 1:
return distance
# Handle nodes that might not have outgoing edges
if current_node < len(adjacency_list):
for neighbor in adjacency_list[current_node]:
if not visited[neighbor]:
visited[neighbor] = True
queue.append(neighbor)
distance += 1
return -1 # No path found (shouldn't happen in this problem)
4. Memory Optimization Consideration
While the current solution is correct, repeatedly creating a new visited
array for each BFS call can be optimized by reusing a single array:
class Solution:
def shortestDistanceAfterQueries(self, n: int, queries: List[List[int]]) -> List[int]:
# Create reusable visited array
self.visited = [False] * n
def bfs(start: int) -> int:
# Reset visited array instead of creating new one
for i in range(n):
self.visited[i] = False
self.visited[start] = True
# ... rest of BFS logic
This reduces memory allocation overhead when processing many queries.
Depth first search is equivalent to which of the tree traversal order?
Recommended Readings
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
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!