Skip to content
ΣDSA Patterns
Menu
Language

Shortest Path (Weighted)

Guide 4 of 6 · Path 4 of 6

Interactive

Mental model

A worked animation for this problem. Scrub steps or press space to pause; re-tell the invariant out loud.

Step 1 of 6
0d0 w11∞2∞3∞

ways[0] = 1

Ways to arrive: n=4 diamond. Roads 0-1, 1-3, 0-2, 2-3 all time 1. Count shortest-time paths 0 → 3, mod 10^9+7.

Demo preview: These solutions pass the automated test suite but have not been human-reviewed. Treat trade-offs and prose as draft.

Number of Ways to Arrive at Destination

Problem (restated)

n intersections (0 … n-1) and bidirectional roads [u, v, time]. Return how many shortest-time paths go from 0 to n-1, modulo 10^9+7.

Intuition

Run Dijkstra for shortest times, and piggy-back a ways[] array. When you find a strictly better time to v, copy ways[u]. When you find another path with the same time, add ways[u].

Approaches

Dijkstra + path counts

Unverified
Time O((n+E) log n)Space O(n+E)

Idea. dist[0] = 0, ways[0] = 1. Relax nd = d + w: if nd < dist[v], set dist and ways[v] = ways[u] and push; if nd == dist[v], ways[v] += ways[u] (mod). Do not push again on the equal case — v is already in the heap from the first discovery.

Walkthrough. Two shortest routes of time 7 into the sink that share a prefix → 4 ways in the official sample (the prefix forks twice).

Trade-offs. Same Dijkstra as LC 743 plus the equal-relax branch. Use 64-bit times (n · 10^9). Skip stale pops so you do not double-count.

Solution
/**
 * Toy PQ: sort + shift. Interview/production: binary heap.
 */
export function countPaths(n: number, roads: number[][]): number {
  const MOD = 1_000_000_007;
  const adj: [number, number][][] = Array.from({ length: n }, () => []);
  for (const [u, v, t] of roads) {
    adj[u]!.push([v, t]);
    adj[v]!.push([u, t]);
  }
  const dist = new Array<number>(n).fill(Infinity);
  const ways = new Array<number>(n).fill(0);
  dist[0] = 0;
  ways[0] = 1;
  const pq: [number, number][] = [[0, 0]];
  while (pq.length) {
    pq.sort((a, b) => a[0]! - b[0]!);
    const [d, u] = pq.shift()!;
    if (d > dist[u]!) continue;
    for (const [v, w] of adj[u]!) {
      const nd = d + w;
      if (nd < dist[v]!) {
        dist[v] = nd;
        ways[v] = ways[u]!;
        pq.push([nd, v]);
      } else if (nd === dist[v]) {
        ways[v] = (ways[v]! + ways[u]!) % MOD;
      }
    }
  }
  return ways[n - 1]!;
}
/**
 * Toy PQ: sort + shift. Interview/production: binary heap.
 */
export function countPaths(n: number, roads: number[][]): number {
  const MOD = 1_000_000_007;
  const adj: [number, number][][] = Array.from({ length: n }, () => []);
  for (const [u, v, t] of roads) {
    adj[u]!.push([v, t]);
    adj[v]!.push([u, t]);
  }
  const dist = new Array<number>(n).fill(Infinity);
  const ways = new Array<number>(n).fill(0);
  dist[0] = 0;
  ways[0] = 1;
  const pq: [number, number][] = [[0, 0]];
  while (pq.length) {
    pq.sort((a, b) => a[0]! - b[0]!);
    const [d, u] = pq.shift()!;
    if (d > dist[u]!) continue;
    for (const [v, w] of adj[u]!) {
      const nd = d + w;
      if (nd < dist[v]!) {
        dist[v] = nd;
        ways[v] = ways[u]!;
        pq.push([nd, v]);
      } else if (nd === dist[v]) {
        ways[v] = (ways[v]! + ways[u]!) % MOD;
      }
    }
  }
  return ways[n - 1]!;
}

Template connection

Dijkstra, with an extra ways[] that updates on a strict improvement (copy) or a tie (add). The heap still keys on distance only.

Reflection