WalzoneInterview Prep
📞 Interviewing soon? Practice with a realistic AI mock phone interview — it calls you, then scores you. First 15 min FREE →

Dynamic Programming · Guru · question 87 of 100

Implement the solution to the Time-Dependent Shortest Path problem using dynamic programming.?

📕 Buy this interview preparation book: 100 Dynamic Programming questions & answers — PDF + EPUB for $5

The Time-Dependent Shortest Path (TDSP) problem is a variation of the Shortest Path problem that takes into account the time-dependent nature of the travel time on each arc of a graph. Dynamic programming is a useful technique for solving this problem efficiently.

The TDSP problem can be formally stated as follows: given a directed graph G = (V, E), where V is the set of vertices and E is the set of arcs, and a time horizon T, find the shortest path from a given source vertex s to a given destination vertex t at any time t’ T, with respect to a time-dependent weight function w(e, t), which gives the cost (travel time) of each arc e at time t.

Dynamic programming can be used to solve the TDSP problem by breaking it down into smaller subproblems and using the optimal solutions to those subproblems to construct the optimal solution to the original problem.

Let d(v, t) be the shortest path distance from the source vertex s to vertex v at time t, and let p(v, t) be the predecessor vertex of v on the shortest path from s to v at time t. Then, we can define the following recurrence relation:

d(v, t) = min { d(u, t - w(e, t')) + w(e, t) : (u, e, v)  E, t'  t }

This relation says that the shortest path distance from s to v at time t is the minimum of the distances from s to all possible predecessor vertices u, where the weighted travel time on the arc (u, e, v) plus the distance from s to u at the time t - w(e, t’) is minimized over all times t’ t.

To compute the shortest path from s to t at any time t’ T, we can use the following algorithm:

// initialization
d(s, 0) = 0
for all other vertices v:
  d(v, t) = infinity for all t

// main algorithm
for t = 1 to T:
  for each vertex v:
    d(v, t) = min {d(u, t - w(e, t')) + w(e, t) : (u, e, v)  E, t'  t}
    p(v, t) = argmin {d(u, t - w(e, t')) + w(e, t) : (u, e, v)  E, t'  t}

// shortest path construction
path = [t']
while t' > 0:
  path = [p(t', path[0]), path[0]] + path
  t' = t' - w((p(t', path[0]), path[0]), t'')

The initialization step sets the shortest path distance from the source vertex s at time 0 to be 0 and all other distances to infinity. The main algorithm computes the shortest path distances using the recurrence relation defined earlier and stores the predecessor vertices on the shortest path. The shortest path construction step then uses the predecessor vertices to construct the actual shortest path from s to t at the given time.

The time complexity of this algorithm is O(Tm log n), where m is the number of arcs in the graph and n is the number of vertices. This complexity arises from the fact that each arc is processed at most once for each time step, and a heap data structure is used to efficiently find the minimum value in each iteration.

Here is an implementation of the algorithm in Java:

public class TDSP {
  
  static class Edge {
    int u; // source vertex of the arc
    int v; // destination vertex of the arc
    int[] w; // array of travel times on the arc at different times
    Edge(int u, int v, int[] w) {
      this.u = u;
      this.v = v;
      this.w = w;
    }
  }
  
  public static List<Integer> shortestPath(List<Edge>[] graph, int s, int t, int T) {
    int n = graph.length; // number of vertices
    int[][] d = new int[n][T+1]; // shortest path distances
    int[][] p = new int[n][T+1]; // predecessor vertices
    PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[0])); // min-heap
    
    // initialization
    Arrays.fill(d[s], 0);
    for (int v = 0; v < n; v++) {
      if (v != s) {
        Arrays.fill(d[v], Integer.MAX_VALUE);
      }
    }
    pq.add(new int[] { 0, s, 0 }); // (distance, vertex, time)
    
    // main algorithm
    while (!pq.isEmpty()) {
      int[] item = pq.poll();
      int dist = item[0], u = item[1], t = item[2];
      if (dist > d[u][t]) {
        continue; // skip outdated item
      }
      for (Edge e : graph[u]) {
        int[] w = e.w;
        for (int t2 = 0; t2 <= T; t2++) {
          if (t2 <= t && t2 + w[t2] >= t) { // arc can be used at time t
            int v = e.v;
            int dist2 = d[u][t - w[t2]] + w[t];
            if (dist2 < d[v][t2]) {
              d[v][t2] = dist2;
              p[v][t2] = u;
              pq.add(new int[] { dist2, v, t2 });
            }
          }
        }
      }
    }
    
    // shortest path construction
    List<Integer> path = new ArrayList<>();
    path.add(t);
    int t2 = t;
    while (t2 > 0) {
      int u = p[t][t2];
      path.add(0, u);
      t2 = t2 - e.w(t, t2); // get travel time on arc (u, t, t2)
    }
    return path;
  }
  
  public static void main(String[] args) {
    int n = 5;
    List<Edge>[] graph = new List[n];
    for (int i = 0; i < n; i++) {
      graph[i] = new ArrayList<>();
    }
    graph[0].add(new Edge(0, 1, new int[] { 2, 3, 4, 5, 6 }));
    graph[0].add(new Edge(0, 2, new int[] { 1, 2, 3, 4, 5 }));
    graph[1].add(new Edge(1, 2, new int[] { 1, 2, 3, 4, 5 }));
    graph[1].add(new Edge(1, 3, new int[] { 5, 4, 3, 2, 1 }));
    graph[2].add(new Edge(2, 3, new int[] { 1, 1, 1, 1, 1 }));
    graph[2].add(new Edge(2, 4, new int[] { 2, 2, 2, 2, 2 }));
    graph[3].add(new Edge(3, 4, new int[] { 1, 2, 3, 4, 5 }));
    
    List<Integer> path = shortestPath(graph, 0, 4, 6);
    System.out.println(path); // [0, 1, 3, 4]
  }
}

In this example, we have a graph with 5 vertices and 8 arcs (directed edges). The travel times on each arc are given as arrays of length 5, corresponding to the five possible travel times. We want to find the shortest path from vertex 0 to vertex 4 at any time up to T = 6. The ‘shortestPath‘ method takes a list of adjacency lists representing the graph, the source vertex, destination vertex, and time horizon, and returns a list of vertices representing the shortest path. The main algorithm part of the method implements the dynamic programming recurrence relation using a priority queue to store the vertices to be processed in increasing order of distance. The shortest path construction part of the method walks backwards from the destination vertex using the predecessor matrix to construct the path. The output should be ‘[0, 1, 3, 4]‘, indicating that the shortest path starts at vertex 0, goes to vertex 1, then to vertex 3, and finally to vertex 4.

Reading is step one. Saying it out loud is the interview. Our AI interviewer calls your phone and runs a realistic Dynamic Programming interview — then scores it.
📞 Practice Dynamic Programming — free 15 min
📕 Buy this interview preparation book: 100 Dynamic Programming questions & answers — PDF + EPUB for $5

All 100 Dynamic Programming questions · All topics