Maximum Path Quality of a Graph
HARDDescription
There is an undirected graph with n nodes numbered from 0 to n - 1 (inclusive). You are given a 0-indexed integer array values where values[i] is the value of the ith node. You are also given a 0-indexed 2D integer array edges, where each edges[j] = [uj, vj, timej] indicates that there is an undirected edge between the nodes uj and vj, and it takes timej seconds to travel between the two nodes. Finally, you are given an integer maxTime.
A valid path in the graph is any path that starts at node 0, ends at node 0, and takes at most maxTime seconds to complete. You may visit the same node multiple times. The quality of a valid path is the sum of the values of the unique nodes visited in the path (each node's value is added at most once to the sum).
Return the maximum quality of a valid path.
Note: There are at most four edges connected to each node.
Example 1:
Input: values = [0,32,10,43], edges = [[0,1,10],[1,2,15],[0,3,10]], maxTime = 49 Output: 75 Explanation: One possible path is 0 -> 1 -> 0 -> 3 -> 0. The total time taken is 10 + 10 + 10 + 10 = 40 <= 49. The nodes visited are 0, 1, and 3, giving a maximal path quality of 0 + 32 + 43 = 75.
Example 2:
Input: values = [5,10,15,20], edges = [[0,1,10],[1,2,10],[0,3,10]], maxTime = 30 Output: 25 Explanation: One possible path is 0 -> 3 -> 0. The total time taken is 10 + 10 = 20 <= 30. The nodes visited are 0 and 3, giving a maximal path quality of 5 + 20 = 25.
Example 3:
Input: values = [1,2,3,4], edges = [[0,1,10],[1,2,11],[2,3,12],[1,3,13]], maxTime = 50 Output: 7 Explanation: One possible path is 0 -> 1 -> 3 -> 1 -> 0. The total time taken is 10 + 13 + 13 + 10 = 46 <= 50. The nodes visited are 0, 1, and 3, giving a maximal path quality of 1 + 2 + 4 = 7.
Constraints:
n == values.length1 <= n <= 10000 <= values[i] <= 1080 <= edges.length <= 2000edges[j].length == 30 <= uj < vj <= n - 110 <= timej, maxTime <= 100- All the pairs
[uj, vj]are unique. - There are at most four edges connected to each node.
- The graph may not be connected.
Approaches
Checkout 2 different approaches to solve Maximum Path Quality of a Graph. Click on different approaches to view the approach and algorithm in detail.
Optimized DFS with Pruning via Dijkstra's Algorithm
This approach significantly improves upon the brute-force DFS by incorporating two key optimizations. First, it maintains the current path's quality incrementally, avoiding costly O(N) recalculations. Second, and more importantly, it uses a powerful pruning strategy. By pre-calculating the shortest time from every node back to node 0 using Dijkstra's algorithm, it can intelligently discard paths that have no chance of returning to the origin within the maxTime limit. This drastically reduces the search space.
Algorithm
- Build an adjacency list
adjfor the graph. - Use Dijkstra's algorithm starting from node 0 to precompute
minTimeToZero[i], the shortest time from every nodeito node 0. - Initialize a global
max_quality = values[0](for the path just staying at 0). - Initialize a frequency array
visited_countsof sizen, withvisited_counts[0] = 1. - Define a recursive function
dfs(u, time_left, visited_counts, current_quality). - In
dfs(u, time_left, ...):- For each neighbor
vofuwith edge timet:- If
time_left >= t:- Pruning Step: Check if
time_left - t >= minTimeToZero[v]. If not, skip this path. - If the check passes, update
visited_countsandnew_quality. - If
v == 0, updatemax_quality = max(max_quality, new_quality). - Make a recursive call:
dfs(v, time_left - t, visited_counts, new_quality). - Backtrack by decrementing
visited_counts[v].
- Pruning Step: Check if
- If
- For each neighbor
- Start the search with
dfs(0, maxTime, visited_counts, values[0]). - Return
max_quality.
The core idea is to avoid exploring futile paths. A path is futile if, from the current node, there isn't enough time left to return to node 0, even by taking the quickest possible route.
1. Preprocessing with Dijkstra's Algorithm:
We first compute the shortest time from every node in the graph to node 0. Since the graph is undirected and times are non-negative, we can run Dijkstra's algorithm starting from node 0. The result is an array, minTimeToZero, where minTimeToZero[i] stores this minimum time for node i.
2. Optimized DFS with Pruning:
We then perform a DFS, but with a much smarter recursive step. The state of our DFS is (u, timeLeft, visitedCounts, currentQuality).
When at a node u and considering a move to a neighbor v that takes time seconds:
- We first check if we have enough time for this single step:
timeLeft >= time. - Pruning: We then apply our key optimization. The time remaining after moving to
vwould betimeLeft - time. To complete a valid path, we must be able to get fromvback to 0. The fastest this can be done isminTimeToZero[v]. Therefore, we must satisfytimeLeft - time >= minTimeToZero[v]. If this condition fails, we prune this entire branch of the search, as no path extending fromvcan be valid.
If the pruning check passes, we proceed with the recursive call, updating the currentQuality in O(1) time. Whenever a path reaches node 0, we treat it as a valid completed path and update our global maxQuality.
class Solution {
int maxQuality = 0;
List<int[]>[] adj;
int[] values;
int[] minTimeToZero;
public int maximalPathQuality(int[] values, int[][] edges, int maxTime) {
this.values = values;
int n = values.length;
adj = new ArrayList[n];
for (int i = 0; i < n; i++) {
adj[i] = new ArrayList<>();
}
for (int[] edge : edges) {
adj[edge[0]].add(new int[]{edge[1], edge[2]});
adj[edge[1]].add(new int[]{edge[0], edge[2]});
}
minTimeToZero = new int[n];
Arrays.fill(minTimeToZero, Integer.MAX_VALUE);
minTimeToZero[0] = 0;
PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[1]));
pq.offer(new int[]{0, 0}); // {node, time}
while (!pq.isEmpty()) {
int[] curr = pq.poll();
int u = curr[0];
int time = curr[1];
if (time > minTimeToZero[u]) {
continue;
}
for (int[] neighbor : adj[u]) {
int v = neighbor[0];
int edgeTime = neighbor[1];
if (minTimeToZero[u] + edgeTime < minTimeToZero[v]) {
minTimeToZero[v] = minTimeToZero[u] + edgeTime;
pq.offer(new int[]{v, minTimeToZero[v]});
}
}
}
int[] visitedCounts = new int[n];
visitedCounts[0] = 1;
this.maxQuality = values[0];
dfs(0, maxTime, visitedCounts, values[0]);
return this.maxQuality;
}
private void dfs(int u, int timeLeft, int[] visitedCounts, int currentQuality) {
for (int[] neighbor : adj[u]) {
int v = neighbor[0];
int time = neighbor[1];
if (timeLeft >= time) {
int newTimeLeft = timeLeft - time;
if (newTimeLeft >= minTimeToZero[v]) {
boolean isNewlyVisited = (visitedCounts[v] == 0);
visitedCounts[v]++;
int newQuality = currentQuality + (isNewlyVisited ? values[v] : 0);
if (v == 0) {
this.maxQuality = Math.max(this.maxQuality, newQuality);
}
dfs(v, newTimeLeft, visitedCounts, newQuality);
visitedCounts[v]--; // Backtrack
}
}
}
}
}
Complexity Analysis
Pros and Cons
- Significantly more efficient than the brute-force approach due to intelligent pruning.
- The O(1) update for path quality avoids the expensive O(N) recalculation.
- Effectively solves the problem within the given constraints.
- More complex to implement due to the need for Dijkstra's algorithm as a preprocessing step.
- The overhead of Dijkstra's algorithm might be noticeable for graphs where the search space is naturally small.
Code Solutions
Checking out 3 solutions in different languages for Maximum Path Quality of a Graph. Click on different languages to view the code.
class Solution {
private
List<int[]>[] g;
private
boolean[] vis;
private
int[] values;
private
int maxTime;
private
int ans;
public
int maximalPathQuality(int[] values, int[][] edges, int maxTime) {
int n = values.length;
g = new List[n];
Arrays.setAll(g, k->new ArrayList<>());
for (var e : edges) {
int u = e[0], v = e[1], t = e[2];
g[u].add(new int[]{v, t});
g[v].add(new int[]{u, t});
}
vis = new boolean[n];
vis[0] = true;
this.values = values;
this.maxTime = maxTime;
dfs(0, 0, values[0]);
return ans;
}
private
void dfs(int u, int cost, int value) {
if (u == 0) {
ans = Math.max(ans, value);
}
for (var e : g[u]) {
int v = e[0], t = e[1];
if (cost + t <= maxTime) {
if (vis[v]) {
dfs(v, cost + t, value);
} else {
vis[v] = true;
dfs(v, cost + t, value + values[v]);
vis[v] = false;
}
}
}
}
}
Video Solution
Watch the video walkthrough for Maximum Path Quality of a Graph
Similar Questions
5 related questions you might find useful
Patterns:
Data Structures:
Companies:
Subscribe to Scale Engineer newsletter
Learn about System Design, Software Engineering, and interview experiences every week.
No spam, unsubscribe at any time.