Routes with an exact total weight
A weighted directed graph has n nodes numbered 0 to n − 1. Each
triple [u, v, w] in edges is an edge from u to v of weight
w; weights may be negative. Return the number of paths from source to
destination that never repeat a node and whose weights add up to exactly target.
Examples
| Input | Output | Why |
|---|---|---|
n = 5, edges = [[0,1,2],[0,3,5],[1,4,2],[2,4,1],[3,2,2],[4,3,1]], source = 0, destination = 3, target = 5 | 2 | 0 → 3 (5) and 0 → 1 → 4 → 3 (2 + 2 + 1). |
same graph, target = 7 | 0 | No route totals 7. |
n = 3, edges = [[0,1,1],[1,2,1],[0,2,2]], source = 0, destination = 2, target = 2 | 2 | Both routes total 2. |
Constraints
- 2 ≤ n ≤ 12
- 0 ≤ edges.length ≤ 40
- −10 ≤ w ≤ 10
- source ≠ destination
Hints
Hint 1
This asks about paths, not reachability, so a node may be used by several different paths: mark it only while it is on the current path.
Hint 2
Carry the running total down the recursion. At the destination, count the path if the total equals the target, then stop: a simple path cannot pass through the destination and come back.
Hint 3
Weights can be negative, so you cannot prune when the running total exceeds the target.
Where people lose marks
- Continuing past the destination, which counts walks that leave it and return.
- Pruning on the running total while weights can be negative.
- Marking nodes visited for the whole search, which counts each node in at most one path.
Dry run on 5,[[0,1,2],[0,3,5],[1,4,2],[2,4,1],[3,2,2],[4,3,1]],0,3,5
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 5current path00paths foundnone yet0
Start at 0, the only node on the path.
-
Step 2 / 5current path0011paths foundnone yet0
Extend the path to 1.
-
Step 3 / 5current path001142paths foundnone yet0
Extend the path to 4.
-
Step 4 / 5current path00114233paths found01430
Reached 3 along 0 → 1 → 4 → 3, total 5: equal to the target, count it. Then back up for other routes.
-
Step 5 / 5current path0031paths found01430031
Reached 3 along 0 → 3, total 5: equal to the target, count it. Then back up for other routes.
How to think about it
Depth-first search over simple paths, carrying the total so far. Entering a node puts it on the path; leaving it takes it off, so that other paths may use it. On reaching the destination, compare the total with the target and return without going further: any longer route would have to pass through the destination twice.
With non-negative weights you could also stop as soon as the running total exceeds the target. Negative weights are allowed here, so that shortcut would miss paths: a total of 8 can come back down to 5.
Complexity. Time O(number of simple paths · n) · Space O(n)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
def count_target_paths(n, edges, source, destination, target):
adj = [[] for _ in range(n)]
for u, v, w in edges:
adj[u].append((v, w))
on_path = [False] * n
def explore(u, total):
if u == destination:
return 1 if total == target else 0 # never continue past the destination
on_path[u] = True
count = 0
for v, w in adj[u]:
if not on_path[v]:
count += explore(v, total + w)
on_path[u] = False # backtrack
return count
return explore(source, 0)#include <functional>
#include <utility>
#include <vector>
long long solve(int n, const std::vector<std::vector<int>>& edges, int source, int destination, int target) {
std::vector<std::vector<std::pair<int, int>>> adj(n);
for (const auto& e : edges) adj[e[0]].push_back({e[1], e[2]});
std::vector<bool> onPath(n, false);
std::function<long long(int, int)> explore = [&](int u, int total) -> long long {
if (u == destination) return total == target ? 1 : 0; // stop at the destination
onPath[u] = true;
long long count = 0;
for (auto [v, w] : adj[u])
if (!onPath[v]) count += explore(v, total + w);
onPath[u] = false; // backtrack
return count;
};
return explore(source, 0);
}import java.util.*;
class Solution {
private List<List<int[]>> adj;
private boolean[] onPath;
private int destination, target;
public long countTargetPaths(int n, int[][] edges, int source, int destination, int target) {
adj = new ArrayList<>();
for (int i = 0; i < n; i++) adj.add(new ArrayList<>());
for (int[] e : edges) adj.get(e[0]).add(new int[]{e[1], e[2]});
onPath = new boolean[n];
this.destination = destination;
this.target = target;
return explore(source, 0);
}
private long explore(int u, int total) {
if (u == destination) return total == target ? 1 : 0; // stop at the destination
onPath[u] = true;
long count = 0;
for (int[] e : adj.get(u))
if (!onPath[e[0]]) count += explore(e[0], total + e[1]);
onPath[u] = false; // backtrack
return count;
}
}function countTargetPaths(n, edges, source, destination, target) {
const adj = Array.from({ length: n }, () => []);
for (const [u, v, w] of edges) adj[u].push([v, w]);
const onPath = new Array(n).fill(false);
const explore = (u, total) => {
if (u === destination) return total === target ? 1 : 0; // stop at the destination
onPath[u] = true;
let count = 0;
for (const [v, w] of adj[u]) if (!onPath[v]) count += explore(v, total + w);
onPath[u] = false; // backtrack
return count;
};
return explore(source, 0);
}fn solve(n: i64, edges: &[[i64; 3]], source: i64, destination: i64, target: i64) -> i64 {
let n = n as usize;
let mut adj = vec![Vec::new(); n];
for e in edges {
adj[e[0] as usize].push((e[1] as usize, e[2]));
}
fn explore(u: usize, total: i64, dest: usize, target: i64, adj: &[Vec<(usize, i64)>], on_path: &mut Vec<bool>) -> i64 {
if u == dest {
return if total == target { 1 } else { 0 }; // stop at the destination
}
on_path[u] = true;
let mut count = 0;
for &(v, w) in &adj[u] {
if !on_path[v] {
count += explore(v, total + w, dest, target, adj, on_path);
}
}
on_path[u] = false; // backtrack
count
}
let mut on_path = vec![false; n];
explore(source as usize, 0, destination as usize, target, &adj, &mut on_path)
}import qualified Data.Map.Strict as M
import qualified Data.Set as S
solve :: Int -> [[Int]] -> Int -> Int -> Int -> Int
solve _ edges source dest target = explore source 0 S.empty
where
adj = M.fromListWith (++) [(u, [(v, w)]) | [u, v, w] <- edges]
explore u total onPath
| u == dest = if total == target then 1 else 0 -- stop at the destination
| otherwise = sum [ explore v (total + w) onPath'
| (v, w) <- M.findWithDefault [] u adj, not (S.member v onPath') ]
where onPath' = S.insert u onPath
Variants to try
- Return the paths themselves, in lexicographic order.
- Weights are all positive and n = 40: prune on the running total and memoise on (node, visited set) where possible.
- Count paths with total at most the target instead of exactly equal.
Next problems
Every route from source to target
List every simple path from node 0 to node n − 1 in a directed graph. Search depth-first and take each node off the path on the way back.
Open the problem → Hard30 minRoutes that visit every node once
Count the paths from source to destination that visit every node exactly once. Backtracking works for small n; a bitmask over visited nodes makes it O(2ⁿ · n²).
Open the problem → Easy12 minCount the islands in a grid
Every unvisited land cell starts a new island; flood it so it is never counted again. The first graph problem most interviews ask.
Open the problem →Write a solution and run it against the real test table.
Keep going
Every route from source to target
List every simple path from node 0 to node n − 1 in a directed graph. Search depth-first and take each node off the path on the way back.
Open the problem → Hard30 minRoutes that visit every node once
Count the paths from source to destination that visit every node exactly once. Backtracking works for small n; a bitmask over visited nodes makes it O(2ⁿ · n²).
Open the problem → Easy12 minCount the islands in a grid
Every unvisited land cell starts a new island; flood it so it is never counted again. The first graph problem most interviews ask.
Open the problem → GraphsThe pattern behind it
Grids, networks and dependencies are all graphs. Breadth-first search finds shortest paths, depth-first search and union-find find what is connected, Dijkstra handles weighted edges, and a queue of nodes with no remaining prerequisites gives an order.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.