Shortest distances from one node
A directed graph has n nodes numbered 0 to n − 1. Each triple
[u, v, w] in edges is an edge from u to v with non-negative
weight w; there may be several edges between the same two nodes. Return a list whose entry
v is the smallest total weight of a path from src to v, or
−1 if v cannot be reached. The entry for src is 0.
Examples
| Input | Output | Why |
|---|---|---|
n = 3, edges = [[0,1,5]], src = 0 | [0, 5, -1] | Node 2 has no incoming edge. |
n = 4, edges = [[0,1,1],[1,2,1],[0,2,5],[2,3,0]], src = 0 | [0, 1, 2, 2] | Two light edges beat one heavy one; a zero-weight edge is allowed. |
n = 4, edges = [[0,1,4],[0,1,1],[1,3,2]], src = 0 | [0, 1, -1, 3] | Parallel edges: the lighter one counts. |
Constraints
- 1 ≤ n ≤ 10⁴
- 0 ≤ edges.length ≤ 5 · 10⁴
- 0 ≤ w ≤ 10⁴
Hints
Hint 1
Build an adjacency list of (neighbour, weight) pairs.
Hint 2
Keep a min-heap of (distance, node). The first time a node is popped, its distance is final.
Hint 3
When a node is popped a second time, the entry is stale: skip it.
Where people lose marks
- Settling a node when it is pushed rather than when it is popped: a cheaper route may still arrive.
- Not skipping stale entries, which re-relaxes edges with an outdated distance (wrong in some implementations, slow in all).
- Using Dijkstra on a graph with a negative weight. Use Bellman–Ford.
- Initialising distances with a sentinel that overflows when a weight is added to it.
Dry run on 6,[[0,1,7],[0,2,2],[2,1,3],[1,3,4],[2,3,8],[3,4,1],[2,4,10],[4,5,3],[3,5,6]],0
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 11heap (distance:node), smallest first0:00
Every distance starts at ∞ except the source 0, at 0. The heap holds (0, 0).
-
Step 2 / 11heap (distance:node), smallest first2:207:11
Pop (0, 0). Nothing left in the heap is smaller, so 0 is final for 0. Relax its edges: 1: ∞ → 7; 2: ∞ → 2.
-
Step 3 / 11heap (distance:node), smallest first5:107:1110:3212:43
Pop (2, 2). Nothing left in the heap is smaller, so 2 is final for 2. Relax its edges: 1: 7 → 5; 3: ∞ → 10; 4: ∞ → 12.
-
Step 4 / 11heap (distance:node), smallest first7:109:3110:3212:43
Pop (5, 1). Nothing left in the heap is smaller, so 5 is final for 1. Relax its edges: 3: 10 → 9.
-
Step 5 / 11heap (distance:node), smallest first9:3010:3112:42
Pop (7, 1): 1 was already settled at 5, so this entry is stale. Skip it.
-
Step 6 / 11heap (distance:node), smallest first10:3010:4112:4215:53
Pop (9, 3). Nothing left in the heap is smaller, so 9 is final for 3. Relax its edges: 4: 12 → 10; 5: ∞ → 15.
-
Step 7 / 11heap (distance:node), smallest first10:4012:4115:52
Pop (10, 3): 3 was already settled at 9, so this entry is stale. Skip it.
-
Step 8 / 11heap (distance:node), smallest first12:4013:5115:52
Pop (10, 4). Nothing left in the heap is smaller, so 10 is final for 4. Relax its edges: 5: 15 → 13.
-
Step 9 / 11heap (distance:node), smallest first13:5015:51
Pop (12, 4): 4 was already settled at 10, so this entry is stale. Skip it.
-
Step 10 / 11heap (distance:node), smallest first15:50
Pop (13, 5). Nothing left in the heap is smaller, so 13 is final for 5. No edge from it improves a distance.
-
Step 11 / 11heap (distance:node), smallest firstempty0
Pop (15, 5): 5 was already settled at 13, so this entry is stale. Skip it. The heap is empty: every reachable distance is final.
How to think about it
Dijkstra's algorithm, as in the lesson. A min-heap holds (tentative distance, node) pairs, starting with (0, src). Each pop yields the unsettled node with the smallest tentative distance, which is final because no weight is negative. Settling it relaxes its outgoing edges, pushing a new pair for every neighbour whose distance improves.
Improvements are pushed as new entries rather than updated in place, so a node may appear in the heap several times; every pop after the first is stale and skipped. Parallel edges need no special treatment: the lighter one simply wins the comparison.
Complexity. Time O((n + m) log n) · Space O(n + m)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
import heapq
def shortest_distances(n, edges, src):
adj = [[] for _ in range(n)]
for u, v, w in edges:
adj[u].append((v, w))
dist = [-1] * n # -1 until settled
heap = [(0, src)]
while heap:
d, u = heapq.heappop(heap)
if dist[u] != -1:
continue # stale entry: u is already final
dist[u] = d # smallest unsettled distance: final
for v, w in adj[u]:
if dist[v] == -1:
heapq.heappush(heap, (d + w, v))
return dist#include <functional>
#include <queue>
#include <utility>
#include <vector>
std::vector<long long> solve(int n, const std::vector<std::vector<int>>& edges, int src) {
std::vector<std::vector<std::pair<int, int>>> adj(n);
for (const auto& e : edges) adj[e[0]].push_back({e[1], e[2]});
std::vector<long long> dist(n, -1); // -1 until settled
using Entry = std::pair<long long, int>;
std::priority_queue<Entry, std::vector<Entry>, std::greater<Entry>> heap;
heap.push({0, src});
while (!heap.empty()) {
auto [d, u] = heap.top();
heap.pop();
if (dist[u] != -1) continue; // stale entry
dist[u] = d; // final
for (auto [v, w] : adj[u])
if (dist[v] == -1) heap.push({d + w, v});
}
return dist;
}import java.util.*;
class Solution {
public int[] shortestDistances(int n, int[][] edges, int src) {
List<List<int[]>> adj = new ArrayList<>();
for (int i = 0; i < n; i++) adj.add(new ArrayList<>());
for (int[] e : edges) adj.get(e[0]).add(new int[]{e[1], e[2]});
int[] dist = new int[n];
Arrays.fill(dist, -1); // -1 until settled
PriorityQueue<int[]> heap = new PriorityQueue<>((a, b) -> Integer.compare(a[0], b[0]));
heap.add(new int[]{0, src});
while (!heap.isEmpty()) {
int[] top = heap.poll();
int d = top[0], u = top[1];
if (dist[u] != -1) continue; // stale entry
dist[u] = d; // final
for (int[] e : adj.get(u))
if (dist[e[0]] == -1) heap.add(new int[]{d + e[1], e[0]});
}
return dist;
}
}function shortestDistances(n, edges, src) {
const adj = Array.from({ length: n }, () => []);
for (const [u, v, w] of edges) adj[u].push([v, w]);
// binary min-heap of [distance, node]
const heap = [];
const push = e => { heap.push(e); let i = heap.length - 1; while (i > 0) { const p = (i - 1) >> 1; if (heap[p][0] <= heap[i][0]) break; [heap[p], heap[i]] = [heap[i], heap[p]]; i = p; } };
const pop = () => { const top = heap[0], last = heap.pop(); if (heap.length) { heap[0] = last; let i = 0; for (;;) { const l = 2 * i + 1, r = l + 1; let m = i; if (l < heap.length && heap[l][0] < heap[m][0]) m = l; if (r < heap.length && heap[r][0] < heap[m][0]) m = r; if (m === i) break; [heap[m], heap[i]] = [heap[i], heap[m]]; i = m; } } return top; };
const dist = new Array(n).fill(-1); // -1 until settled
push([0, src]);
while (heap.length) {
const [d, u] = pop();
if (dist[u] !== -1) continue; // stale entry
dist[u] = d; // final
for (const [v, w] of adj[u]) if (dist[v] === -1) push([d + w, v]);
}
return dist;
}use std::cmp::Reverse;
use std::collections::BinaryHeap;
fn solve(n: i64, edges: &[[i64; 3]], src: i64) -> Vec<i64> {
let n = n as usize;
let mut adj = vec![Vec::new(); n];
for e in edges {
adj[e[0] as usize].push((e[1] as usize, e[2]));
}
let mut dist = vec![-1i64; n]; // -1 until settled
let mut heap = BinaryHeap::new();
heap.push(Reverse((0i64, src as usize)));
while let Some(Reverse((d, u))) = heap.pop() {
if dist[u] != -1 {
continue; // stale entry
}
dist[u] = d; // final
for &(v, w) in &adj[u] {
if dist[v] == -1 {
heap.push(Reverse((d + w, v)));
}
}
}
dist
}import qualified Data.Map.Strict as M
import qualified Data.Set as S
solve :: Int -> [[Int]] -> Int -> [Int]
solve n edges src = [M.findWithDefault (-1) v final | v <- [0 .. n - 1]]
where
adj = M.fromListWith (++) [(u, [(v, w)]) | [u, v, w] <- edges]
final = go (S.singleton (0, src)) M.empty
-- the set of (distance, node) is the heap; the map holds settled nodes
go heap dist = case S.minView heap of
Nothing -> dist
Just ((d, u), rest)
| M.member u dist -> go rest dist -- stale entry
| otherwise ->
let dist' = M.insert u d dist
next = [(d + w, v) | (v, w) <- M.findWithDefault [] u adj, not (M.member v dist')]
in go (foldr S.insert rest next) dist'
Variants to try
- Return the path to a target as well as its length, by recording each node's predecessor.
- Stop as soon as the target is popped: its distance is already final.
- Count the number of distinct shortest paths to each node (add counts when a tie is found).
Next problems
Time for a signal to reach every node
Breadth-first search counts edges; when edges have travel times, a heap settles the nearest node first.
Open the problem → Medium20 minShortest distances with negative edges
Return the shortest distance from a source to every node when weights can be negative, or report a negative cycle the source can reach.
Open the problem → Medium25 minCheapest fare with at most k stops
Find the cheapest route that changes plane at most k times. Plain Dijkstra fails here; k + 1 rounds of Bellman-Ford do not.
Open the problem →Write a solution and run it against the real test table.
Keep going
Time for a signal to reach every node
Breadth-first search counts edges; when edges have travel times, a heap settles the nearest node first.
Open the problem → Medium20 minShortest distances with negative edges
Return the shortest distance from a source to every node when weights can be negative, or report a negative cycle the source can reach.
Open the problem → Medium25 minCheapest fare with at most k stops
Find the cheapest route that changes plane at most k times. Plain Dijkstra fails here; k + 1 rounds of Bellman-Ford do not.
Open the problem → GraphsThe pattern behind it
Grids, networks and dependencies are all graphs. Breadth-first search finds shortest paths, depth-first search and union-find find what is connected, Dijkstra handles weighted edges, and a queue of nodes with no remaining prerequisites gives an order.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.