Maximum Path Quality of a Graph

hard backtracking graph dfs

Problem

An undirected graph has n nodes. values[i] is the value of node i, and each edge [u, v, time] takes time seconds to cross. A valid path starts at node 0, ends at node 0, and takes at most maxTime seconds; you may revisit nodes. The quality of a path is the sum of the values of the unique nodes it visits (each value counted at most once). Return the maximum quality of any valid path.

Each node has at most four incident edges, and every edge costs at least 10 seconds while maxTime ≤ 100, so any valid path crosses at most ten edges — the search space is small enough to explore exhaustively.

Inputvalues = [0,32,10,43], edges = [[0,1,10],[1,2,15],[0,3,10]], maxTime = 49
Output75
Path 0 → 1 → 0 → 3 → 0 costs 10+10+10+10 = 40 ≤ 49 and visits nodes {0, 1, 3}: 0 + 32 + 43 = 75.

def maximalPathQuality(values, edges, maxTime):
    n = len(values)
    adj = [[] for _ in range(n)]          # adjacency list
    for u, v, t in edges:
        adj[u].append((v, t))
        adj[v].append((u, t))
    visited = [False] * n
    best = 0

    def dfs(node, time_left, quality):
        nonlocal best
        if node == 0:                     # back at start: candidate answer
            best = max(best, quality)
        for nxt, cost in adj[node]:
            if cost <= time_left:         # enough time to cross
                seen = visited[nxt]
                gain = 0 if seen else values[nxt]
                visited[nxt] = True       # mark before recursing
                dfs(nxt, time_left - cost, quality + gain)
                if not seen:
                    visited[nxt] = False  # undo only what we set

    visited[0] = True                     # node 0 starts visited
    dfs(0, maxTime, values[0])
    return best
function maximalPathQuality(values, edges, maxTime) {
  const n = values.length;
  const adj = Array.from({ length: n }, () => []); // adjacency list
  for (const [u, v, t] of edges) {
    adj[u].push([v, t]);
    adj[v].push([u, t]);
  }
  const visited = new Array(n).fill(false);
  let best = 0;

  function dfs(node, timeLeft, quality) {
    if (node === 0) best = Math.max(best, quality); // candidate answer
    for (const [nxt, cost] of adj[node]) {
      if (cost <= timeLeft) {            // enough time to cross
        const seen = visited[nxt];
        const gain = seen ? 0 : values[nxt];
        visited[nxt] = true;             // mark before recursing
        dfs(nxt, timeLeft - cost, quality + gain);
        if (!seen) visited[nxt] = false; // undo only what we set
      }
    }
  }

  visited[0] = true;                     // node 0 starts visited
  dfs(0, maxTime, values[0]);
  return best;
}
int best;
int maximalPathQuality(int[] values, int[][] edges, int maxTime) {
    int n = values.length;
    List<int[]>[] adj = new List[n];     // adjacency list
    for (int i = 0; i < n; i++) adj[i] = new ArrayList<>();
    for (int[] e : edges) {
        adj[e[0]].add(new int[]{e[1], e[2]});
        adj[e[1]].add(new int[]{e[0], e[2]});
    }
    boolean[] visited = new boolean[n];
    best = 0;
    visited[0] = true;                   // node 0 starts visited
    dfs(adj, values, visited, 0, maxTime, values[0]);
    return best;
}

void dfs(List<int[]>[] adj, int[] values, boolean[] visited,
         int node, int timeLeft, int quality) {
    if (node == 0) best = Math.max(best, quality); // candidate answer
    for (int[] nb : adj[node]) {
        int nxt = nb[0], cost = nb[1];
        if (cost <= timeLeft) {          // enough time to cross
            boolean seen = visited[nxt];
            int gain = seen ? 0 : values[nxt];
            visited[nxt] = true;         // mark before recursing
            dfs(adj, values, visited, nxt, timeLeft - cost, quality + gain);
            if (!seen) visited[nxt] = false; // undo only what we set
        }
    }
}
int best = 0;
void dfs(vector<vector<pair<int,int>>>& adj, vector<int>& values,
         vector<bool>& visited, int node, int timeLeft, int quality) {
    if (node == 0) best = max(best, quality); // candidate answer
    for (auto& [nxt, cost] : adj[node]) {
        if (cost <= timeLeft) {          // enough time to cross
            bool seen = visited[nxt];
            int gain = seen ? 0 : values[nxt];
            visited[nxt] = true;         // mark before recursing
            dfs(adj, values, visited, nxt, timeLeft - cost, quality + gain);
            if (!seen) visited[nxt] = false; // undo only what we set
        }
    }
}
int maximalPathQuality(vector<int>& values, vector<vector<int>>& edges, int maxTime) {
    int n = values.size();
    vector<vector<pair<int,int>>> adj(n);  // adjacency list
    for (auto& e : edges) {
        adj[e[0]].push_back({e[1], e[2]});
        adj[e[1]].push_back({e[0], e[2]});
    }
    vector<bool> visited(n, false);
    best = 0;
    visited[0] = true;                   // node 0 starts visited
    dfs(adj, values, visited, 0, maxTime, values[0]);
    return best;
}
Time: O(n + |E| · 4(maxTime/minEdge)) Space: O(n + maxTime/minEdge)