Problem

There are n cities connected by some number of flights. You are given an array flights where flights[i] = [fromi, toi, pricei] indicates that there is a flight from city fromi to city toi with cost pricei.

You are also given three integers srcdst, and k, return the cheapest price from src to dst with at most k stops. If there is no such route, return -1.

Examples

Example 1:

Input: n = 4, flights = [ [0,1,100],[1,2,100],[2,0,100],[1,3,600],[2,3,200] ], src = 0, dst = 3, k = 1
Output: 700
Explanation:
The graph is shown above.
The optimal path with at most 1 stop from city 0 to 3 is marked in red and has cost 100 + 600 = 700.
Note that the path through cities [0,1,2,3] is cheaper but is invalid because it uses 2 stops.

Example 2:

graph TD;
A(0) -- "100" --> B(1)
B -- "100" --> C(2)
A -- "500" --> C

linkStyle default stroke:blue
linkStyle 0 stroke-width:4px,stroke:red
linkStyle 1 stroke-width:4px,stroke:red
  
Input: n = 3, flights = [ [0,1,100],[1,2,100],[0,2,500] ], src = 0, dst = 2, k = 1
Output: 200
Explanation:
The graph is shown above.
The optimal path with at most 1 stop from city 0 to 2 is marked in red and has cost 100 + 100 = 200.

Example 3:

graph TD;
A(0) -- "100" --> B(1)
B -- "100" --> C(2)
A -- "500" --> C

linkStyle default stroke:blue
linkStyle 2 stroke-width:4px,stroke:red
  
Input: n = 3, flights = [ [0,1,100],[1,2,100],[0,2,500] ], src = 0, dst = 2, k = 0
Output: 500
Explanation:
The graph is shown above.
The optimal path with no stops from city 0 to 2 is marked in red and has cost 500.

Constraints

  • 1 <= n <= 100
  • 0 <= flights.length <= (n * (n - 1) / 2)
  • flights[i].length == 3
  • 0 <= fromi, toi < n
  • fromi != toi
  • 1 <= pricei <= 10^4
  • There will not be any multiple flights between two cities.
  • 0 <= src, dst, k < n
  • src != dst

Solution

Constraints of the flights are super important. One of them is k<n. There are multiple solutions to this problem:

  • Dijkstra’s Algo - O(V^2)
  • Floyd Warshall - O(V^3)
  • Bellman Ford - O(V^3) - Takes care of negative cycle, but not needed as flight costs are positive
  • BFS ( + optional Heap)
  • DFS + Pruning - Easy
  • DFS + Memo - Easy

Also, how will we represent our graph? This is a weighted graph. We can use following representation: Weighted Graph Representation - Adjacency List of Pair and map.

Lets look at the common build graph function:

public Map<Integer, List<int[]>> buildGraph(int[][] flights) {
	for (int i = 0; i < n; i++) {
		graph.put(i, new LinkedList<>());
	}
	
	Map<Integer,List<int[]>> map=new HashMap<>();
	for(int[] f:flights) {
		// map.putIfAbsent(f[0],new ArrayList<>());
		map.get(f[0]).add(new int[]{i[1],i[2]});
	}

} 

Note that we are running for (i=0; i<n;i++) loop above. The reason is it helps in case when the graph is not connected. If we are sure that graph is connected, we can just remove this loop and uncomment map.putIfAbsent(f[0],new ArrayList<>());.

Now we can use buildGraph across multiple approaches

Method 1 - DFS + Pruning

Pretty straightforward implementation. Traverse all the children of the source upto k stops. If at any point we reach the destination, store the min of the answer and the current cost.

Now the question is why are we passing flights as K+1 to DFS? Because we have K stops at maximum, hence K+1 flights between those stops.

Code

Java
public int findCheapestPrice(int n, int[][] flights, int src, int dst, int K) {
	Map<Integer, List<int[]>> graph = buildGraph(n, flights);
	boolean[] visited = new boolean[n];

	// I am passing fare as array object as Java is pass by value
	int[] fare = new int[1];
	fare[0] = Integer.MAX_VALUE;
	
	//find cheapest price with k stops(k + 1 flights)
	dfs(graph, src, dst, K+1, fare, 0, visited);

	return fare[0] == Integer.MAX_VALUE ? -1 : fare[0];
}

private void dfs(Map<Integer, List<int[]>> graph, int src, int dst, int K, int[] fare, int currentCost, boolean[] visited) {
	if (K == 0) {
		return;
	}
	if (src == dst) {
		fare[0] = Math.min(fare[0], currentCost);
		return;
	}

	visited[src] = true;

	for (int[] e : graph.get(src)) {
		int eVertex = e[0], eCost = e[1];
		//Pruning, check the sum of current price and next cost. 
		if (!visited[eVertex] && currentCost + eCost <= fare[0]) {
			dfs(graph, eVertex, dst, K - 1, fare, currentCost + eCost, visited);
		}
	}

	visited[src] = false;
}

Note that we are doing pruning by checking currentCost+eCost <= fare[0], otherwise the algo will become slower.

Method 2 - DFS + Memo

The only thing which appears to be different from a standard DFS call is the memoization part. Since, the nodes are higher in number and we can easily get a TLE re-calculating the distances, it’s better to store the computed values for later use.

So, we now return cost from dfs method. As, we are storing fare in dp array, we dont have to carry fare as well and remove visited set.

Code

Java
public int findCheapestPrice(int n, int[][] flights, int src, int dst, int K) {
	Map<Integer, List<int[]>> graph = buildGraph(n, flights);

	int[][] dp = new int[n+1][K+2];
	
	//find cheapest price with k stops(k + 1 flights)
	int ans = dfs(graph, src, dst, K+1, dp);

	return ans;
}

private int dfs(Map<Integer, List<int[]>> graph, int src, int dst, int K, int[][] dp) {
	if (src == dst) {
		return 0;
	}

	if (K == 0) {
		return Integer.MAX_VALUE;
	}

	if (dp[src][K] != 0) return dp[src][K];

	
	int minCost = Integer.MAX_VALUE
	for (int[] e : graph.get(src)) {
		int eVertex = e[0], eCost = e[1];
		
		int currCostRemaining = dfs(graph, eVertex, dst, K - 1, dp);
		ans = Math.min(ans, eCost+currCostRemaining);
		
	}

	dp[src][K] = ans;
	return ans;
}

Method 3 - Bottom up Tabular DP

No need to build graph here.

Code

Java
public int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) {
	int[][] dp = new int[k+2][n];
	for(int i=0; i<=k+1; i++){
		Arrays.fill(dp[i],Integer.MAX_VALUE);    
	}
	//fly from src to scr cost 0
	for(int i=0; i<=k+1; i++){
		dp[i][src] = 0;    
	}
	
	for(int i=1; i<=k+1; i++){
		for(int[] f: flights){
			int u = f[0];
			int v = f[1];
			int w = f[2];
			if(dp[i-1][u]!=Integer.MAX_VALUE){
				dp[i][v] = Math.min(dp[i][v],dp[i-1][u]+w);
			}
		}
	}
	return dp[k+1][dst] == Integer.MAX_VALUE ? -1 : dp[k+1][dst];
}

Method 4 - BFS + Pruning

Unlike BFS, now we simultaneously traverse all the possible path going out from source for upto k steps. If the ans is found in between, we store the min of the current ans with the newly found one. A modification to the standard bfs design, we pass the starting cost a 0 to the queue as well and go on adding to it.

Code

Java
public int findCheapestPrice(int n, int[][] flights, int src, int dst, int K) {
	Map<Integer, List<int[] >> graph = buildGraph(n, flights);
	int step = 0;
	Queue<int[] > q = new LinkedList<>();
	q.offer(new int[] {
		src, 0
	});
	int ans = Integer.MAX_VALUE;
	while (!q.isEmpty()) {
		int size = q.size();
		for (int i = 0; i<size; i++) {
			int[] curr = q.poll();
			if (curr[0] == dst)
				ans = Math.min(ans, curr[1]);
			if (!map.containsKey(curr[0]))
				continue;
			for (int[] f: map.get(curr[0])) {
				if (curr[1] + f[1] > ans) //Pruning
					continue;
				q.offer(new int[] {
					f[0], curr[1] + f[1]
				});
			}
		}
		if (step > (K+1)) // again remember, K stops, K+1 flights
			break;
	}
	return ans == Integer.MAX_VALUE ? -1 : ans;
}

Method 5 - Dijkstra’s Algo

Much like BFS, but use a PriorityQueue based on the cheapest cost. Incorporate the stop limit to individual paths to traverse upto k stops.

Code

Java
public int findCheapestPrice(int n, int[][] flights, int src, int dst, int K) {
	Map<Integer, List<int[] >> map = buildGraph(n, flights);
	PriorityQueue<int[] > q = new PriorityQueue<>((a,b) -> Integer.compare(a[0],b[0]));
	
	q.offer(new int[] {
		0, src, K + 1
	});
	while (!q.isEmpty()) {
		int[] c = q.poll();
		int cost = c[0];
		int curr = c[1];
		int stop = c[2];
		if (curr == dst)
			return cost;
		if (stop > 0) {
			for (int[] e: map.get(curr)) {
				int eVertex = e[0], eCost = e[1];
				q.add(new int[] {
					cost + eCost, eVertex, stop - 1
				});
			}
		}
	}
	return -1;
}

Method 6 - Bellman Ford

Much like BFS, run the algorithm K times, if the answer exists, it should be stored in the helper matrix

Code

Java
public int findCheapestPrice(int n, int[][] flights, int src, int dst, int K) {
	int[] cost = new int[n];
	Arrays.fill(cost, Integer.MAX_VALUE);
	cost[src] = 0;
	for (int i = 0; i<= K; i++) {
		int[] temp = Arrays.copyOf(cost, n);
		for (int[] f: flights) {
			int curr = f[0], next = f[1], price = f[2];
			if (cost[curr] == Integer.MAX_VALUE)
				continue;
			temp[next] = Math.min(temp[next], cost[curr] + price);
		}
		cost = temp;
	}
	return cost[dst] == Integer.MAX_VALUE ? -1 : cost[dst];
}