Skip to content
ΣDSA Patterns
Menu
Language

Shortest Path (Weighted)

Guide 5 of 6 · Path 5 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
1src2dst

leave only while green

Second minimum time: n=2, one edge, time=3, change=2. Lights: change min green, then change min red, starting green at t=0.

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

Second Minimum Time to Reach Destination

Problem (restated)

An undirected unweighted graph on nodes 1…n. Traversing any edge takes time minutes. Each node has a traffic light: change minutes green, then change minutes red, starting green at t=0. You may leave a node only while green (wait if you arrive on red). Return the second-minimum arrival time at n from 1.

Intuition

You need two distinct arrival times at n, so each node stores a first and a second time (strictly larger). Dijkstra/BFS keyed by arrival. Before leaving, if ⌊t / change⌋ is odd, wait until the next green: t += change - t % change. Then every neighbor arrives at t + time.

Approaches

First and second arrival

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

Idea. dist1[v], dist2[v]. On a candidate nt: if nt < dist1[v], shift first down to second and set first; else if dist1[v] < nt < dist2[v], set second. Push in both cases. The first time you pop n with t > dist1[n], that t is the answer. Equal times are ignored (not “second”).

Walkthrough. n=2, one edge, time=3, change=2. First arrival at 2 is 3. Leave 2 after waiting to 4, back to 1 at 7, wait to 8, back to 2 at 11.

Trade-offs. Do not finalize a node on first visit — the second visit is the point. Waiting uses the departure time; stored dist is the arrival. 64-bit times: time and change go to 10^9.

Solution
/**
 * Toy PQ: sort + shift. Interview/production: binary heap.
 * Each node keeps first and second arrival; leave only on green.
 */
export function secondMinimum(
  n: number,
  edges: number[][],
  time: number,
  change: number,
): number {
  const adj: number[][] = Array.from({ length: n + 1 }, () => []);
  for (const [a, b] of edges) {
    adj[a]!.push(b);
    adj[b]!.push(a);
  }
  const dist1 = new Array<number>(n + 1).fill(Infinity);
  const dist2 = new Array<number>(n + 1).fill(Infinity);
  dist1[1] = 0;
  const pq: [number, number][] = [[0, 1]];
  while (pq.length) {
    pq.sort((a, b) => a[0]! - b[0]!);
    let [t, u] = pq.shift()!;
    if (u === n && t > dist1[n]!) return t;
    if (Math.floor(t / change) % 2 === 1) t += change - (t % change);
    for (const v of adj[u]!) {
      const nt = t + time;
      if (nt < dist1[v]!) {
        dist2[v] = dist1[v]!;
        dist1[v] = nt;
        pq.push([nt, v]);
      } else if (nt > dist1[v]! && nt < dist2[v]!) {
        dist2[v] = nt;
        pq.push([nt, v]);
      }
    }
  }
  return dist2[n]!;
}
/**
 * Toy PQ: sort + shift. Interview/production: binary heap.
 * Each node keeps first and second arrival; leave only on green.
 */
export function secondMinimum(
  n: number,
  edges: number[][],
  time: number,
  change: number,
): number {
  const adj: number[][] = Array.from({ length: n + 1 }, () => []);
  for (const [a, b] of edges) {
    adj[a]!.push(b);
    adj[b]!.push(a);
  }
  const dist1 = new Array<number>(n + 1).fill(Infinity);
  const dist2 = new Array<number>(n + 1).fill(Infinity);
  dist1[1] = 0;
  const pq: [number, number][] = [[0, 1]];
  while (pq.length) {
    pq.sort((a, b) => a[0]! - b[0]!);
    let [t, u] = pq.shift()!;
    if (u === n && t > dist1[n]!) return t;
    if (Math.floor(t / change) % 2 === 1) t += change - (t % change);
    for (const v of adj[u]!) {
      const nt = t + time;
      if (nt < dist1[v]!) {
        dist2[v] = dist1[v]!;
        dist1[v] = nt;
        pq.push([nt, v]);
      } else if (nt > dist1[v]! && nt < dist2[v]!) {
        dist2[v] = nt;
        pq.push([nt, v]);
      }
    }
  }
  return dist2[n]!;
}

Template connection

Dijkstra + extra state, here a second distance per node instead of (node, k). Same “do not stop at first pop” idea as LC 787.

Reflection