3244. Shortest Distance After Road Addition Queries II
Problem Description
You have n
cities numbered from 0
to n - 1
. Initially, these cities are connected by unidirectional 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
with initial length n - 1
.
You are given a list of queries
where each query queries[i] = [u_i, v_i]
represents adding a new unidirectional road from city u_i
to city v_i
. These new roads act as shortcuts that can potentially reduce the distance from city 0
to city n - 1
.
After processing each query (adding each new road), you need to determine the length of the shortest path from city 0
to city n - 1
.
The problem guarantees that there are no two queries with overlapping but non-nested intervals. Specifically, there won't be cases where queries[i][0] < queries[j][0] < queries[i][1] < queries[j][1]
.
Your task is to return an array answer
where answer[i]
represents the shortest path length from city 0
to city n - 1
after processing the first i + 1
queries.
For example, if n = 5
, you initially have roads: 0β1β2β3β4
, giving a path length of 4. If you add a road from city 1
to city 3
, you create a shortcut 0β1β3β4
, reducing the path length to 3.
Intuition
The key insight is that when we add a new road from city u
to city v
, we're essentially creating a shortcut that allows us to skip cities u+1, u+2, ..., v-1
. This shortcut reduces the total path length.
Initially, the shortest path from city 0
to city n-1
requires visiting every city in sequence, giving us a path length of n-1
. Each time we add a shortcut, we can potentially reduce this length.
Think of it this way: if we maintain a record of "which city comes next" for each city, we can track the actual path. The array nxt[i]
tells us the next city we can reach from city i
. Initially, nxt[i] = i + 1
because we can only go to the next consecutive city.
When we add a road from u
to v
, we update nxt[u] = v
, meaning from city u
, we can now jump directly to city v
. But here's the crucial part: the cities that were previously between u
and v
in our path are now bypassed. We need to mark these bypassed cities somehow.
The clever trick is to set nxt[i] = 0
for all cities that get bypassed. This indicates that these cities are no longer part of any shortest path from city 0
to city n-1
.
By counting how many cities remain "active" (not bypassed) in our path, we can maintain the current shortest distance. Each time we bypass a city, we decrease our path length counter by 1.
There's also an optimization: if we already have a shortcut that covers a larger range, adding a smaller shortcut within that range won't help. For instance, if we already have a road from city 1
to city 5
, adding a road from city 2
to city 4
won't reduce the path length further. This is why we check if 0 < nxt[u] < v
before processing a query.
Solution Approach
Let's walk through the implementation step by step:
1. Initialize the tracking array:
We create an array nxt
where nxt[i]
represents the next city reachable from city i
. Initially, nxt = [1, 2, 3, ..., n-1]
, meaning each city i
connects to city i+1
.
2. Initialize the path length counter:
We maintain cnt = n - 1
, which represents the current shortest path length from city 0
to city n-1
.
3. Process each query [u, v]
:
For each new road from city u
to city v
, we first check if this shortcut is meaningful:
- The condition
0 < nxt[u] < v
checks whether:nxt[u] > 0
: Cityu
is still active (not bypassed)nxt[u] < v
: The new road actually creates a shortcut (jumps further than the current next city)
4. Update bypassed cities:
If the shortcut is valid, we need to mark all cities that will be bypassed. Starting from i = nxt[u]
(the current next city from u
), we iterate through all cities until we reach v
:
i = nxt[u] while i < v: cnt -= 1 # Decrease path length for each bypassed city nxt[i], i = 0, nxt[i] # Mark city as bypassed (set to 0) and move to next
The clever use of simultaneous assignment nxt[i], i = 0, nxt[i]
ensures we:
- Save the next city to visit before overwriting it
- Mark the current city as bypassed by setting
nxt[i] = 0
- Move to the next city in the original path
5. Update the shortcut:
After marking all bypassed cities, we set nxt[u] = v
to establish the new shortcut.
6. Record the result:
After processing each query, we append the current cnt
to our answer array.
Example walkthrough:
If n = 5
and we add a road from city 1
to city 4
:
- Initial:
nxt = [1, 2, 3, 4]
,cnt = 4
- Cities
2
and3
will be bypassed - We set
nxt[2] = 0
,nxt[3] = 0
, andnxt[1] = 4
cnt
decreases by 2 (for the two bypassed cities), becoming2
- The new shortest path is:
0 β 1 β 4
, with length 2
This greedy approach works because once a city is bypassed, it will never be part of any future shortest path, allowing us to maintain the answer efficiently in O(1)
amortized time per query.
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 with n = 6
cities (numbered 0 to 5) and queries [[0, 3], [2, 5], [0, 4]]
.
Initial Setup:
- Cities:
0 β 1 β 2 β 3 β 4 β 5
nxt = [1, 2, 3, 4, 5]
(each city points to the next)cnt = 5
(need 5 steps to go from city 0 to city 5)- Path length: 5
Query 1: [0, 3] - Add road from city 0 to city 3
- Check if shortcut is valid:
0 < nxt[0] < 3
β0 < 1 < 3
β - Cities to bypass: 1 and 2
- Start with
i = nxt[0] = 1
- While
1 < 3
: Setnxt[1] = 0
, decreasecnt
to 4, move toi = 2
- While
2 < 3
: Setnxt[2] = 0
, decreasecnt
to 3, move toi = 3
- Stop (3 is not < 3)
- Start with
- Set
nxt[0] = 3
(create the shortcut) - New path:
0 β 3 β 4 β 5
nxt = [3, 0, 0, 4, 5]
,cnt = 3
- Answer so far:
[3]
Query 2: [2, 5] - Add road from city 2 to city 5
- Check if shortcut is valid:
0 < nxt[2] < 5
β0 < 0 < 5
β - City 2 is already bypassed (nxt[2] = 0), so this shortcut has no effect
- Path remains:
0 β 3 β 4 β 5
nxt = [3, 0, 0, 4, 5]
,cnt = 3
- Answer so far:
[3, 3]
Query 3: [0, 4] - Add road from city 0 to city 4
- Check if shortcut is valid:
0 < nxt[0] < 4
β0 < 3 < 4
β - Cities to bypass: 3
- Start with
i = nxt[0] = 3
- While
3 < 4
: Setnxt[3] = 0
, decreasecnt
to 2, move toi = 4
- Stop (4 is not < 4)
- Start with
- Set
nxt[0] = 4
(create the shortcut) - New path:
0 β 4 β 5
nxt = [4, 0, 0, 0, 5]
,cnt = 2
- Answer so far:
[3, 3, 2]
Final Result: [3, 3, 2]
The algorithm efficiently tracks which cities are active in the shortest path. Once a city is bypassed (marked with 0), it never becomes part of the path again, allowing us to maintain the shortest distance by simply counting active transitions.
Solution Implementation
1from typing import List
2
3class Solution:
4 def shortestDistanceAfterQueries(
5 self, n: int, queries: List[List[int]]
6 ) -> List[int]:
7 # Initialize next_node array where next_node[i] points to the next reachable node from i
8 # Initially, each node i points to i+1 (forming a path 0->1->2->...->n-1)
9 next_node = list(range(1, n))
10
11 # Result array to store shortest distance after each query
12 result = []
13
14 # Current shortest path length (initially n-1 edges from 0 to n-1)
15 current_distance = n - 1
16
17 # Process each query which adds a shortcut from u to v
18 for start_node, end_node in queries:
19 # Check if adding this edge creates a valid shortcut
20 # The edge is valid if:
21 # 1. next_node[start_node] is not 0 (not already bypassed)
22 # 2. next_node[start_node] < end_node (creates an actual shortcut)
23 if 0 < next_node[start_node] < end_node:
24 # Start from the node that start_node currently points to
25 current = next_node[start_node]
26
27 # Mark all nodes between start_node and end_node as bypassed
28 # These nodes are no longer part of the shortest path
29 while current < end_node:
30 # Decrease the distance count for each bypassed edge
31 current_distance -= 1
32 # Mark the node as bypassed (0) and move to the next node
33 temp = next_node[current]
34 next_node[current] = 0
35 current = temp
36
37 # Update start_node to directly point to end_node (add the shortcut)
38 next_node[start_node] = end_node
39
40 # Append the current shortest distance after processing this query
41 result.append(current_distance)
42
43 return result
44
1class Solution {
2 public int[] shortestDistanceAfterQueries(int n, int[][] queries) {
3 // Initialize array to track the next node in the shortest path
4 // nextNode[i] represents the next node from node i in the current shortest path
5 // Initially, each node i points to node i+1 (sequential path)
6 int[] nextNode = new int[n - 1];
7 for (int i = 1; i < n; ++i) {
8 nextNode[i - 1] = i;
9 }
10
11 // Track the current shortest distance from node 0 to node n-1
12 // Initially it's n-1 (sequential path through all nodes)
13 int currentShortestDistance = n - 1;
14
15 // Prepare result array to store shortest distance after each query
16 int queryCount = queries.length;
17 int[] result = new int[queryCount];
18
19 // Process each query (shortcut edge)
20 for (int queryIndex = 0; queryIndex < queryCount; ++queryIndex) {
21 int startNode = queries[queryIndex][0];
22 int endNode = queries[queryIndex][1];
23
24 // Check if adding this edge creates a valid shortcut
25 // A shortcut is valid if:
26 // 1. The current next node from startNode exists (nextNode[startNode] > 0)
27 // 2. The current next node is before the new endNode
28 if (nextNode[startNode] > 0 && nextNode[startNode] < endNode) {
29 // Remove all intermediate nodes that will be bypassed by this shortcut
30 int currentNode = nextNode[startNode];
31 while (currentNode < endNode) {
32 // Decrease the shortest distance count for each bypassed node
33 --currentShortestDistance;
34
35 // Store the next node before marking current as bypassed
36 int tempNextNode = nextNode[currentNode];
37
38 // Mark this node as bypassed (0 indicates no longer in shortest path)
39 nextNode[currentNode] = 0;
40
41 // Move to the next node
42 currentNode = tempNextNode;
43 }
44
45 // Update the shortcut: startNode now directly connects to endNode
46 nextNode[startNode] = endNode;
47 }
48
49 // Store the current shortest distance after processing this query
50 result[queryIndex] = currentShortestDistance;
51 }
52
53 return result;
54 }
55}
56
1class Solution {
2public:
3 vector<int> shortestDistanceAfterQueries(int n, vector<vector<int>>& queries) {
4 // Initialize next array where next[i] points to the next node in the path
5 // Initially forms a linear path: 0 -> 1 -> 2 -> ... -> n-1
6 vector<int> next(n - 1);
7 iota(next.begin(), next.end(), 1);
8
9 // Track the current shortest distance (number of edges in the path)
10 int currentDistance = n - 1;
11
12 // Store results after each query
13 vector<int> result;
14
15 // Process each query that adds a new edge
16 for (const auto& query : queries) {
17 int fromNode = query[0];
18 int toNode = query[1];
19
20 // Only process if this creates a valid shortcut
21 // Check if fromNode is active and the new edge creates a longer jump
22 if (next[fromNode] != 0 && next[fromNode] < toNode) {
23 // Remove intermediate nodes that will be bypassed
24 int currentNode = next[fromNode];
25 while (currentNode < toNode) {
26 // Decrease distance count for each bypassed edge
27 --currentDistance;
28
29 // Save the next node before invalidating current
30 int nextNode = next[currentNode];
31
32 // Mark this node as bypassed (no longer in the shortest path)
33 next[currentNode] = 0;
34
35 // Move to the next node
36 currentNode = nextNode;
37 }
38
39 // Create the new shortcut edge
40 next[fromNode] = toNode;
41 }
42
43 // Record the shortest distance after this query
44 result.push_back(currentDistance);
45 }
46
47 return result;
48 }
49};
50
1/**
2 * Finds the shortest distance after applying each query that adds a shortcut edge
3 * @param n - Number of nodes in the graph (0 to n-1)
4 * @param queries - Array of [u, v] pairs representing new edges from u to v
5 * @returns Array of shortest distances after each query
6 */
7function shortestDistanceAfterQueries(n: number, queries: number[][]): number[] {
8 // Initialize next array where next[i] points to the next node in the path
9 // Initially, each node i points to i+1 (forming a simple path 0->1->2->...->n-1)
10 const nextNode: number[] = Array.from({ length: n - 1 }, (_, index) => index + 1);
11
12 // Result array to store shortest distance after each query
13 const result: number[] = [];
14
15 // Current count of edges in the shortest path (initially n-1 edges)
16 let edgeCount: number = n - 1;
17
18 // Process each query
19 for (const [startNode, endNode] of queries) {
20 // Check if adding edge from startNode to endNode creates a valid shortcut
21 // Only process if startNode has a next node and it's less than endNode
22 if (nextNode[startNode] && nextNode[startNode] < endNode) {
23 // Remove intermediate nodes between startNode and endNode
24 let currentNode: number = nextNode[startNode];
25
26 // Iterate through all nodes that will be bypassed
27 while (currentNode < endNode) {
28 // Decrease edge count as we're removing this edge
29 --edgeCount;
30
31 // Store the next node before removing the edge
32 const tempNext: number = nextNode[currentNode];
33
34 // Mark current node as disconnected (0 means no next node)
35 nextNode[currentNode] = 0;
36
37 // Move to the next node
38 currentNode = tempNext;
39 }
40
41 // Create the new shortcut edge from startNode directly to endNode
42 nextNode[startNode] = endNode;
43 }
44
45 // Add current shortest distance (edge count) to result
46 result.push(edgeCount);
47 }
48
49 return result;
50}
51
Time and Space Complexity
Time Complexity: O(n + q)
The algorithm initializes a nxt
array in O(n)
time. For each query, it processes nodes between positions u
and v
. The key insight is that each node can only be "skipped over" (set to 0) at most once throughout all queries. When nxt[i]
is set to 0, that node is effectively removed from future traversals. Since there are n-1
nodes that can be skipped (excluding the first node), and each query takes constant time when no nodes are skipped, the total time across all q
queries is bounded by O(n + q)
.
Space Complexity: O(n)
The algorithm uses a nxt
array of size n-1
to track the next reachable city from each position, and an ans
array to store results (which has size q
). The dominant space usage is the nxt
array, giving us O(n)
space complexity. The temporary variables i
, u
, v
, and cnt
use constant space.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Loop Termination When Marking Bypassed Nodes
The Pitfall: A common mistake is incorrectly handling the loop that marks bypassed nodes. Developers might write:
# WRONG - This can cause infinite loop or skip nodes current = next_node[start_node] while current < end_node: current_distance -= 1 next_node[current] = 0 current = next_node[current] # Bug: next_node[current] was just set to 0!
The problem here is that after setting next_node[current] = 0
, we try to use next_node[current]
to get the next node, but it's now 0, causing the loop to terminate prematurely or behave unexpectedly.
The Solution: Use simultaneous assignment or save the next node before modifying:
# CORRECT - Option 1: Simultaneous assignment current = next_node[start_node] while current < end_node: current_distance -= 1 next_node[current], current = 0, next_node[current] # CORRECT - Option 2: Save next node first current = next_node[start_node] while current < end_node: current_distance -= 1 temp = next_node[current] next_node[current] = 0 current = temp
2. Not Checking if a Shortcut is Actually Valid
The Pitfall: Blindly adding every query as a shortcut without checking if it actually improves the path:
# WRONG - Processes invalid shortcuts for start_node, end_node in queries: current = next_node[start_node] while current < end_node: current_distance -= 1 next_node[current], current = 0, next_node[current] next_node[start_node] = end_node result.append(current_distance)
This fails when:
- The node is already bypassed (
next_node[start_node] == 0
) - The shortcut doesn't actually skip any nodes (
next_node[start_node] >= end_node
)
The Solution: Always validate the shortcut before processing:
# CORRECT - Validates shortcut conditions for start_node, end_node in queries: if 0 < next_node[start_node] < end_node: # Process valid shortcut current = next_node[start_node] while current < end_node: current_distance -= 1 next_node[current], current = 0, next_node[current] next_node[start_node] = end_node result.append(current_distance)
3. Off-by-One Error in Initial Setup
The Pitfall:
Incorrectly initializing the next_node
array size or values:
# WRONG - Creates array of wrong size
next_node = list(range(n)) # Should be range(1, n)
# WRONG - Includes n as a destination
next_node = list(range(1, n+1)) # Node n-1 shouldn't point to n
The Solution: Remember that:
- We have n cities numbered 0 to n-1
- City n-1 is the destination (doesn't need a next pointer)
- The array should have n-1 elements
# CORRECT
next_node = list(range(1, n)) # [1, 2, 3, ..., n-1]
This creates an array where next_node[i]
represents the next city from city i
for i
in range [0, n-2]
.
In a binary min heap, the minimum element can be found in:
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
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!