Shortest Distance After Road Addition Queries II
HardPrompt
You are given an integer n and a 2D integer array queries.
There are n cities numbered from 0 to n - 1. Initially, there is a unidirectional road from city i to city i + 1 for all 0 <= i < n - 1.
queries[i] = [ui, vi] represents the addition of a new unidirectional road from city ui to city vi. After each query, you need to find the length of the shortest path from city 0 to city n - 1.
There are no two queries such that queries[i][0] < queries[j][0] < queries[i][1] < queries[j][1].
Return an array answer where for each i in the range [0, queries.length - 1], answer[i] is the length of the shortest path from city 0 to city n - 1 after processing the first i + 1 queries.
Example 1:
Input: n = 5, queries = [[2,4],[0,2],[0,4]]
Output: [3,2,1]
Explanation:

After the addition of the road from 2 to 4, the length of the shortest path from 0 to 4 is 3.

After the addition of the road from 0 to 2, the length of the shortest path from 0 to 4 is 2.

After the addition of the road from 0 to 4, the length of the shortest path from 0 to 4 is 1.
Example 2:
Input: n = 4, queries = [[0,3],[0,2]]
Output: [1,1]
Explanation:

After the addition of the road from 0 to 3, the length of the shortest path from 0 to 3 is 1.

After the addition of the road from 0 to 2, the length of the shortest path remains 1.
Constraints:
3 <= n <= 1051 <= queries.length <= 105queries[i].length == 20 <= queries[i][0] < queries[i][1] < n1 < queries[i][1] - queries[i][0]- There are no repeated roads among the queries.
- There are no two queries such that
i != jandqueries[i][0] < queries[j][0] < queries[i][1] < queries[j][1].
Approaches
3 approaches with complexity analysis and trade-offs.
The most straightforward approach is to simulate the process directly. After each query adds a new road, the graph is modified. We can construct the graph with all roads up to the current query and then run a Breadth-First Search (BFS) starting from city 0 to find the shortest path to city n-1. Since all road lengths are 1, BFS is guaranteed to find the shortest path in terms of the number of roads.
Algorithm
- Initialize an adjacency list to represent the graph. Add the initial roads
i -> i+1for all0 <= i < n-1. - Create an array
answerto store the results. - Iterate through each query
[u, v]: a. Add the new directed edgeu -> vto the adjacency list. b. Perform a BFS starting from the source city0: i. Initialize adistancearray of sizenwith a value indicating infinity (e.g., -1), and setdistance[0] = 0. ii. Use a queue for BFS and add0to it. iii. While the queue is not empty, dequeue a citycurr. Ifcurrisn-1, we have found the shortest path length. iv. For each neighbor ofcurr, if it hasn't been visited (i.e., its distance is infinity), update its distance and enqueue it. c. After the BFS completes,distance[n-1]will hold the length of the shortest path from0ton-1. Store this value in theanswerarray. - Return the
answerarray.
Walkthrough
Algorithm:
- Initialize an adjacency list to represent the graph. Add the initial roads
i -> i+1for all0 <= i < n-1. - Create an array
answerto store the results. - Iterate through each query
[u, v]: a. Add the new directed edgeu -> vto the adjacency list. b. Perform a BFS starting from the source city0: i. Initialize adistancearray of sizenwith a value indicating infinity (e.g., -1), and setdistance[0] = 0. ii. Use a queue for BFS and add0to it. iii. While the queue is not empty, dequeue a citycurr. Ifcurrisn-1, we have found the shortest path length. iv. For each neighbor ofcurr, if it hasn't been visited (i.e., its distance is infinity), update its distance and enqueue it. c. After the BFS completes,distance[n-1]will hold the length of the shortest path from0ton-1. Store this value in theanswerarray. - Return the
answerarray.
Code Snippet:
class Solution { public int[] shortestDistanceAfterQueries(int n, int[][] queries) { List<List<Integer>> adj = new ArrayList<>(); for (int i = 0; i < n; i++) { adj.add(new ArrayList<>()); } for (int i = 0; i < n - 1; i++) { adj.get(i).add(i + 1); } int[] result = new int[queries.length]; for (int i = 0; i < queries.length; i++) { adj.get(queries[i][0]).add(queries[i][1]); int[] dist = new int[n]; Arrays.fill(dist, -1); Queue<Integer> q = new LinkedList<>(); dist[0] = 0; q.add(0); int pathLength = -1; while (!q.isEmpty()) { int u = q.poll(); if (u == n - 1) { pathLength = dist[u]; break; } for (int v : adj.get(u)) { if (dist[v] == -1) { dist[v] = dist[u] + 1; q.add(v); } } } result[i] = pathLength; } return result; }}Complexity
Time
O(Q * (N + E)) where N is the number of cities and E is the number of edges. After `k` queries, `E = (N-1) + k`. So, the complexity is roughly O(Q * (N + Q)). Given N, Q <= 10^5, this is too slow.
Space
O(N + Q) to store the adjacency list and O(N) for the BFS queue and distance array.
Trade-offs
Pros
Simple to understand and implement.
Correctly solves the problem by definition.
Cons
Highly inefficient due to redundant computations. The BFS is run from scratch for every query.
Instead of rebuilding the graph and running a full BFS each time, we can maintain an array dist where dist[i] stores the shortest distance from city 0 to city i. Initially, dist[i] = i. After each query [u, v], we only need to re-calculate the distances for cities from v onwards, as distances to cities before v cannot be improved by this new road. This can be framed as a dynamic programming problem.
Algorithm
- Initialize an empty list of query edges seen so far.
- For each new query
[u, v]: a. Add the query to the list of edges. b. Create adistarray of sizento compute the new shortest paths. c. Setdist[0] = 0. d. Iterate fromi = 1ton-1to computedist[i]: i. The default path is fromi-1, so initializedist[i] = dist[i-1] + 1. ii. Consider all query edges(u_j, v_j)added so far wherev_j == i. For each such edge, there's a potential shorter path throughu_j. Updatedist[i] = min(dist[i], dist[u_j] + 1). e. The shortest path ton-1isdist[n-1]. Add it to the results.
Walkthrough
Algorithm:
- Initialize an empty list of query edges seen so far.
- For each new query
[u, v]: a. Add the query to the list of edges. b. Create adistarray of sizento compute the new shortest paths. c. Setdist[0] = 0. d. Iterate fromi = 1ton-1to computedist[i]: i. The default path is fromi-1, so initializedist[i] = dist[i-1] + 1. ii. Consider all query edges(u_j, v_j)added so far wherev_j == i. For each such edge, there's a potential shorter path throughu_j. Updatedist[i] = min(dist[i], dist[u_j] + 1). e. The shortest path ton-1isdist[n-1]. Add it to the results.
This approach re-calculates the entire dist array for each query, but it's slightly more optimized than a full BFS as it avoids explicit graph traversal overhead.
Code Snippet:
class Solution { public int[] shortestDistanceAfterQueries(int n, int[][] queries) { int[] result = new int[queries.length]; List<int[]> currentQueries = new ArrayList<>(); for (int i = 0; i < queries.length; i++) { currentQueries.add(queries[i]); Map<Integer, List<Integer>> incomingEdges = new HashMap<>(); for (int[] q : currentQueries) { incomingEdges.computeIfAbsent(q[1], k -> new ArrayList<>()).add(q[0]); } long[] dist = new long[n]; dist[0] = 0; for (int j = 1; j < n; j++) { dist[j] = dist[j - 1] + 1; if (incomingEdges.containsKey(j)) { for (int u : incomingEdges.get(j)) { dist[j] = Math.min(dist[j], dist[u] + 1); } } } result[i] = (int) dist[n - 1]; } return result; }}Complexity
Time
O(Q * (N + Q)). For each of the `Q` queries, we iterate from `i=1` to `N-1`. At each `i`, we might iterate through all `k` previous queries (where `k` is the current query index). This leads to a total complexity of roughly `sum_{k=1 to Q} O(N+k) = O(NQ + Q^2)`.
Space
O(Q) to store the queries and O(N) for the `dist` array.
Trade-offs
Pros
Avoids the overhead of a full graph traversal data structure like a queue.
Conceptually builds upon the problem's path structure.
Cons
Still very inefficient and will time out for large inputs.
The inner loop checking all previous queries for each node is costly.
The most efficient solution involves a different dynamic programming perspective. Instead of computing distances to each city, we define a DP state on the queries themselves. Let dp[k] be the length of the shortest path from city 0 to city v_k, with the constraint that the query q_k = (u_k, v_k) is the last shortcut taken. To calculate dp[k], we need the shortest distance to u_k, which can be found by considering all prior shortcuts. This process can be optimized using a segment tree.
Algorithm
We can process queries online. For each query k, we need to find min_{j<k, v_j <= u_k} (dp[j] - v_j). This is a range minimum query. We can use a segment tree for this. The segment tree will be built over city indices 0 to n-1.
- Initialize a segment tree of size
nto handle range minimum queries and point updates. All values are initialized to infinity. - Initialize
min_overall_cost = infinityto trackmin_{j=0..k} (dp[j] - v_j). - For each query
k = (u_k, v_k): a. Query the segment tree for the minimum value in the range[0, u_k]. Let this bem. b. Calculatedp[k] = u_k + 1 + min(0, m). c. Update the segment tree at indexv_kwith the valuedp[k] - v_k. The update operation should bemin(current_value, new_value). d. Update the overall minimum:min_overall_cost = min(min_overall_cost, dp[k] - v_k). e. The shortest path from0ton-1after querykis(n-1) + min(0, min_overall_cost). Store this in the answer array. - Return the answer array.
Walkthrough
DP Formulation:
The shortest path to any city x is dist(0, x). A path can use the initial i -> i+1 roads or any available query shortcuts. A path that uses shortcuts consists of segments of the initial path, connected by query edges.
Let dp[k] be the shortest path from 0 to v_k using query k as the last shortcut. This path looks like 0 -> ... -> u_k -> v_k. The length is dist(0, u_k) + 1.
The shortest distance dist(0, u_k) is the minimum of taking the straight path (u_k) or using some previous query j (j < k) as a shortcut. If we use query j=(u_j, v_j), the path is 0 -> ... -> v_j -> ... -> u_k, with length dp[j] + (u_k - v_j). We only consider queries where v_j <= u_k.
So, dist(0, u_k) = min(u_k, min_{j<k, v_j <= u_k} (dp[j] + u_k - v_j)). Rearranging, dist(0, u_k) = u_k + min(0, min_{j<k, v_j <= u_k} (dp[j] - v_j)).
This gives the recurrence: dp[k] = u_k + 1 + min(0, min_{j<k, v_j <= u_k} (dp[j] - v_j)).
Algorithm with Segment Tree:
We can process queries online. For each query k, we need to find min_{j<k, v_j <= u_k} (dp[j] - v_j). This is a range minimum query. We can use a segment tree for this. The segment tree will be built over city indices 0 to n-1.
- Initialize a segment tree of size
nto handle range minimum queries and point updates. All values are initialized to infinity. - Initialize
min_overall_cost = infinityto trackmin_{j=0..k} (dp[j] - v_j). - For each query
k = (u_k, v_k): a. Query the segment tree for the minimum value in the range[0, u_k]. Let this bem. b. Calculatedp[k] = u_k + 1 + min(0, m). c. Update the segment tree at indexv_kwith the valuedp[k] - v_k. The update operation should bemin(current_value, new_value). d. Update the overall minimum:min_overall_cost = min(min_overall_cost, dp[k] - v_k). e. The shortest path from0ton-1after querykis(n-1) + min(0, min_overall_cost). Store this in the answer array. - Return the answer array.
Code Snippet:
class Solution { public int[] shortestDistanceAfterQueries(int n, int[][] queries) { SegmentTree st = new SegmentTree(n); int[] result = new int[queries.length]; long minCostTerm = Long.MAX_VALUE / 2; for (int i = 0; i < queries.length; i++) { int u = queries[i][0]; int v = queries[i][1]; long m = st.query(0, u); long dp_i = (long)u + 1 + Math.min(0, m); st.update(v, dp_i - v); minCostTerm = Math.min(minCostTerm, dp_i - v); result[i] = (int)((long)n - 1 + Math.min(0, minCostTerm)); } return result; }} class SegmentTree { long[] tree; int n; final long INF = Long.MAX_VALUE / 2; SegmentTree(int size) { n = size; tree = new long[4 * n]; Arrays.fill(tree, INF); } void update(int idx, long val) { update(1, 0, n - 1, idx, val); } void update(int node, int start, int end, int idx, long val) { if (start == end) { tree[node] = Math.min(tree[node], val); return; } int mid = start + (end - start) / 2; if (start <= idx && idx <= mid) { update(2 * node, start, mid, idx, val); } else { update(2 * node + 1, mid + 1, end, idx, val); } tree[node] = Math.min(tree[2 * node], tree[2 * node + 1]); } long query(int l, int r) { return query(1, 0, n - 1, l, r); } long query(int node, int start, int end, int l, int r) { if (r < start || end < l) { return INF; } if (l <= start && end <= r) { return tree[node]; } int mid = start + (end - start) / 2; long p1 = query(2 * node, start, mid, l, r); long p2 = query(2 * node + 1, mid + 1, end, l, r); return Math.min(p1, p2); }}Complexity
Time
O(Q * log N). For each of the `Q` queries, we perform one range query and one point update on the segment tree, both of which take O(log N) time.
Space
O(N + Q). O(N) for the segment tree and O(Q) for the result array.
Trade-offs
Pros
Highly efficient and passes within the time limits.
Correctly solves the problem in an online fashion.
Demonstrates an advanced application of dynamic programming and data structures.
Cons
The DP formulation is non-trivial to derive.
Implementation of the segment tree adds complexity.
Solutions
Solution
class Solution {public int[] shortestDistanceAfterQueries(int n, int[][] queries) { int[] nxt = new int[n - 1]; for (int i = 1; i < n; ++i) { nxt[i - 1] = i; } int m = queries.length; int cnt = n - 1; int[] ans = new int[m]; for (int i = 0; i < m; ++i) { int u = queries[i][0], v = queries[i][1]; if (nxt[u] > 0 && nxt[u] < v) { int j = nxt[u]; while (j < v) { --cnt; int t = nxt[j]; nxt[j] = 0; j = t; } nxt[u] = v; } ans[i] = cnt; } return ans; }}class Solution {public: vector<int> shortestDistanceAfterQueries(int n, vector<vector<int>> &queries) { vector<int> nxt(n - 1); iota(nxt.begin(), nxt.end(), 1); int cnt = n - 1; vector<int> ans; for (const auto &q : queries) { int u = q[0], v = q[1]; if (nxt[u] && nxt[u] < v) { int i = nxt[u]; while (i < v) { --cnt; int t = nxt[i]; nxt[i] = 0; i = t; } nxt[u] = v; } ans.push_back(cnt); } return ans; }};class Solution: def shortestDistanceAfterQueries(self, n: int, queries: List[List[int]]) -> List[int]: nxt = list(range(1, n)) ans = [] cnt = n - 1 for u, v in queries: if 0 < nxt[u] < v: i = nxt[u] while i < v: cnt -= 1 nxt[i], i = 0, nxt[i] nxt[u] = v ans . append(cnt) return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.