Facebook Pixel

1575. Count All Possible Routes

Problem Description

You have an array locations containing distinct positive integers, where locations[i] represents the position of city i. You're given three additional parameters:

  • start: the index of your starting city
  • finish: the index of your destination city
  • fuel: the initial amount of fuel you have

Movement Rules:

  • From any city i, you can move to any other city j (where j ≠ i)
  • Moving from city i to city j costs |locations[i] - locations[j]| fuel (the absolute difference between their positions)
  • You cannot make a move if it would cause your fuel to become negative
  • You can visit any city multiple times, including the start and finish cities

Goal: Count all possible routes from the start city to the finish city without running out of fuel.

Important Notes:

  • A route is considered valid as long as it ends at the finish city with non-negative fuel remaining
  • You get credit for reaching the finish city each time you arrive there - if you're already at the finish city, that counts as one valid route, and you can continue traveling to accumulate more routes
  • Return the total count modulo 10^9 + 7 since the answer might be very large

Example Understanding: If you start at city start and it happens to be the same as finish, that immediately counts as one valid route. You can then travel to other cities and return to finish again, with each arrival counting as another valid route, as long as you have sufficient fuel for each journey.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that this is a path counting problem where we need to explore all possible routes. Since we can revisit cities multiple times, we might end up calculating the same subproblem repeatedly - for instance, arriving at the same city with the same amount of fuel through different routes.

Think about it this way: if we're at city i with k fuel remaining, the number of valid routes from this state to the finish doesn't depend on how we got to city i - it only depends on our current position and remaining fuel. This observation suggests using dynamic programming with memoization.

We can break down the problem recursively:

  • From any city i with k fuel, we need to count all possible ways to reach the finish
  • We can try moving to every other city j, which costs |locations[i] - locations[j]| fuel
  • For each valid move (where we have enough fuel), we recursively count the routes from the new position

The base cases emerge naturally:

  1. If we're at the finish city, we've found one valid route (but we can continue traveling to find more)
  2. If we don't have enough fuel to reach the finish city even by the shortest possible path (k < |locations[i] - locations[finish]|), then there are no valid routes from this state

The beauty of this approach is that by using memoization with @cache, we avoid recalculating the number of routes from the same (city, fuel) state multiple times. Each unique state is computed only once, making the solution efficient despite the potentially large number of possible paths.

The recursive function dfs(i, k) naturally captures this idea: "How many ways can I reach the finish from city i with k fuel?" The answer accumulates by trying all possible next moves and summing up their route counts.

Learn more about Memoization and Dynamic Programming patterns.

Solution Approach

The solution uses memoization with depth-first search (DFS) to count all possible routes efficiently.

Core Function Design:

We define dfs(i, k) to represent the number of valid routes from city i to the finish city with k units of fuel remaining. The answer to our problem is simply dfs(start, fuel).

Implementation Steps:

  1. Early Termination Check:

    • First, we check if k < |locations[i] - locations[finish]|
    • This means we don't have enough fuel to reach the finish even by the most direct route
    • If true, return 0 as no valid routes exist from this state
  2. Initialize Route Count:

    • Set ans = 1 if i == finish (we're already at the destination), otherwise ans = 0
    • This accounts for the current position being a valid route if it's the finish
  3. Explore All Possible Moves:

    • Iterate through all cities j using enumerate(locations)
    • For each city j where j != i:
      • Calculate fuel cost: |locations[i] - locations[j]|
      • Recursively call dfs(j, k - fuel_cost) to get routes from the new position
      • Add the result to ans (with modulo operation to prevent overflow)
  4. Memoization with @cache:

    • The @cache decorator automatically stores results of dfs(i, k) calls
    • When the same (i, k) state is encountered again, the cached result is returned immediately
    • This prevents redundant calculations and reduces time complexity significantly

Why This Works:

  • The recursive structure naturally explores all possible paths
  • Each valid route that reaches the finish contributes 1 to the total count
  • By summing contributions from all possible next moves, we count every unique route exactly once
  • The memoization ensures we compute each unique state (city, fuel) only once, even though many different paths might lead to the same state

Time Complexity: O(n² × fuel) where n is the number of cities, as we have at most n × fuel states and each state examines n cities.

Space Complexity: O(n × fuel) for the memoization cache and recursion stack.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example to illustrate the solution approach.

Example Input:

  • locations = [2, 3, 6, 8, 4] (city positions)
  • start = 1 (starting at city index 1, position 3)
  • finish = 3 (destination at city index 3, position 8)
  • fuel = 5

Step-by-Step DFS Exploration:

Starting with dfs(1, 5) - we're at city 1 (position 3) with 5 fuel:

  1. Early termination check: Distance to finish = |3 - 8| = 5. We have exactly 5 fuel, so we can potentially reach it.

  2. Initialize count: Since we're not at finish (1 ≠ 3), ans = 0

  3. Explore moves from city 1:

    • To city 0 (position 2): costs |3 - 2| = 1 fuel → call dfs(0, 4)
    • To city 2 (position 6): costs |3 - 6| = 3 fuel → call dfs(2, 2)
    • To city 3 (position 8): costs |3 - 8| = 5 fuel → call dfs(3, 0)
    • To city 4 (position 4): costs |3 - 4| = 1 fuel → call dfs(4, 4)

Following one branch - dfs(3, 0):

  • We're at city 3 (the finish!) with 0 fuel
  • Early termination check: Distance to finish = 0, we have 0 fuel, so we pass
  • Initialize count: Since we're at finish, ans = 1 (found one route!)
  • Try to explore moves: With 0 fuel, we can't move anywhere
  • Return 1

Following another branch - dfs(0, 4):

  • We're at city 0 (position 2) with 4 fuel
  • Early termination check: Distance to finish = |2 - 8| = 6, but we only have 4 fuel
  • This means we can never reach the finish from here
  • Return 0 immediately

Following dfs(2, 2):

  • We're at city 2 (position 6) with 2 fuel
  • Early termination check: Distance to finish = |6 - 8| = 2, we have exactly 2 fuel
  • Initialize count: Not at finish, so ans = 0
  • Explore moves:
    • To city 3 (position 8): costs |6 - 8| = 2 fuel → call dfs(3, 0) (returns 1)
    • Other moves cost too much fuel
  • Return 1

Memoization in Action: When we call dfs(3, 0) from different paths (like from city 1 directly or via city 2), the second call returns the cached result immediately without recalculation.

Final Result: The total count accumulates from all valid paths. In this example, we find 2 valid routes:

  1. City 1 → City 3 (direct route using all 5 fuel)
  2. City 1 → City 2 → City 3 (using 3 + 2 = 5 fuel)

The function returns 2 as the total number of valid routes.

Solution Implementation

1class Solution:
2    def countRoutes(
3        self, locations: List[int], start: int, finish: int, fuel: int
4    ) -> int:
5        MOD = 10**9 + 7
6      
7        @cache
8        def dfs(current_city: int, remaining_fuel: int) -> int:
9            """
10            Calculate the number of routes from current_city to finish with remaining_fuel.
11          
12            Args:
13                current_city: Index of the current city in locations array
14                remaining_fuel: Amount of fuel remaining
15          
16            Returns:
17                Number of valid routes modulo MOD
18            """
19            # If we don't have enough fuel to reach the finish city even directly,
20            # there are no valid routes from here
21            if remaining_fuel < abs(locations[current_city] - locations[finish]):
22                return 0
23          
24            # Initialize route count - if we're at the finish, this counts as one route
25            route_count = 1 if current_city == finish else 0
26          
27            # Try moving to every other city
28            for next_city in range(len(locations)):
29                if next_city != current_city:
30                    # Calculate fuel cost to move to next_city
31                    fuel_cost = abs(locations[current_city] - locations[next_city])
32                  
33                    # Recursively count routes from next_city with reduced fuel
34                    route_count = (route_count + dfs(next_city, remaining_fuel - fuel_cost)) % MOD
35          
36            return route_count
37      
38        # Start the DFS from the starting city with full fuel
39        return dfs(start, fuel)
40
1class Solution {
2    // Array to store city locations
3    private int[] locations;
4    // Target city index
5    private int finish;
6    // Total number of cities
7    private int n;
8    // Memoization table: dp[cityIndex][remainingFuel]
9    private Integer[][] dp;
10    // Modulo value for result
11    private final int MOD = (int) 1e9 + 7;
12
13    /**
14     * Counts the number of possible routes from start to finish with given fuel limit.
15     * 
16     * @param locations Array of city locations
17     * @param start Starting city index
18     * @param finish Target city index
19     * @param fuel Initial fuel amount
20     * @return Number of possible routes modulo 10^9 + 7
21     */
22    public int countRoutes(int[] locations, int start, int finish, int fuel) {
23        this.n = locations.length;
24        this.locations = locations;
25        this.finish = finish;
26        this.dp = new Integer[n][fuel + 1];
27      
28        return dfs(start, fuel);
29    }
30
31    /**
32     * Depth-first search with memoization to count valid routes.
33     * 
34     * @param currentCity Current city index
35     * @param remainingFuel Remaining fuel amount
36     * @return Number of valid routes from current state
37     */
38    private int dfs(int currentCity, int remainingFuel) {
39        // Early termination: not enough fuel to reach finish city
40        int minFuelNeeded = Math.abs(locations[currentCity] - locations[finish]);
41        if (remainingFuel < minFuelNeeded) {
42            return 0;
43        }
44      
45        // Return memoized result if already computed
46        if (dp[currentCity][remainingFuel] != null) {
47            return dp[currentCity][remainingFuel];
48        }
49      
50        // Initialize result: add 1 if currently at finish city
51        int result = (currentCity == finish) ? 1 : 0;
52      
53        // Try moving to all other cities
54        for (int nextCity = 0; nextCity < n; nextCity++) {
55            if (nextCity != currentCity) {
56                int fuelCost = Math.abs(locations[currentCity] - locations[nextCity]);
57                result = (result + dfs(nextCity, remainingFuel - fuelCost)) % MOD;
58            }
59        }
60      
61        // Memoize and return result
62        dp[currentCity][remainingFuel] = result;
63        return result;
64    }
65}
66
1class Solution {
2public:
3    int countRoutes(vector<int>& locations, int start, int finish, int fuel) {
4        int n = locations.size();
5      
6        // dp[i][j] represents the number of routes from city i with j fuel remaining
7        // -1 indicates unvisited state
8        int dp[n][fuel + 1];
9        memset(dp, -1, sizeof(dp));
10      
11        const int MOD = 1e9 + 7;
12      
13        // DFS with memoization to count all possible routes
14        function<int(int, int)> dfs = [&](int currentCity, int remainingFuel) -> int {
15            // Optimization: if we don't have enough fuel to reach the finish city directly,
16            // we can't reach it at all from current position
17            if (remainingFuel < abs(locations[currentCity] - locations[finish])) {
18                return 0;
19            }
20          
21            // Return memoized result if already computed
22            if (dp[currentCity][remainingFuel] != -1) {
23                return dp[currentCity][remainingFuel];
24            }
25          
26            // Initialize route count: add 1 if we're at the finish city
27            int routeCount = (currentCity == finish) ? 1 : 0;
28          
29            // Try moving to every other city
30            for (int nextCity = 0; nextCity < n; ++nextCity) {
31                if (nextCity != currentCity) {
32                    int fuelCost = abs(locations[currentCity] - locations[nextCity]);
33                    // Recursively count routes from the next city with reduced fuel
34                    routeCount = (routeCount + dfs(nextCity, remainingFuel - fuelCost)) % MOD;
35                }
36            }
37          
38            // Memoize and return the result
39            return dp[currentCity][remainingFuel] = routeCount;
40        };
41      
42        // Start DFS from the starting city with full fuel
43        return dfs(start, fuel);
44    }
45};
46
1/**
2 * Counts the number of routes from start to finish with given fuel constraint
3 * Uses dynamic programming with memoization to avoid recalculating subproblems
4 * 
5 * @param locations - Array of location positions
6 * @param start - Starting location index
7 * @param finish - Destination location index  
8 * @param fuel - Available fuel amount
9 * @returns Number of possible routes modulo 10^9 + 7
10 */
11function countRoutes(locations: number[], start: number, finish: number, fuel: number): number {
12    const locationCount: number = locations.length;
13  
14    // Memoization table: memo[currentLocation][remainingFuel] = number of routes
15    // Initialize with -1 to indicate uncomputed states
16    const memo: number[][] = Array.from(
17        { length: locationCount }, 
18        () => Array(fuel + 1).fill(-1)
19    );
20  
21    const MODULO: number = 1e9 + 7;
22  
23    /**
24     * Depth-first search with memoization to count valid routes
25     * 
26     * @param currentLocation - Current location index
27     * @param remainingFuel - Remaining fuel amount
28     * @returns Number of valid routes from current state to finish
29     */
30    const dfs = (currentLocation: number, remainingFuel: number): number => {
31        // Pruning: If remaining fuel cannot reach finish location, no valid routes exist
32        const minimumFuelNeeded: number = Math.abs(locations[currentLocation] - locations[finish]);
33        if (remainingFuel < minimumFuelNeeded) {
34            return 0;
35        }
36      
37        // Return memoized result if already computed
38        if (memo[currentLocation][remainingFuel] !== -1) {
39            return memo[currentLocation][remainingFuel];
40        }
41      
42        // Initialize route count: if at finish location, count this as one valid route
43        let routeCount: number = currentLocation === finish ? 1 : 0;
44      
45        // Try moving to all other locations
46        for (let nextLocation = 0; nextLocation < locationCount; nextLocation++) {
47            // Skip if trying to stay at same location
48            if (nextLocation !== currentLocation) {
49                // Calculate fuel cost to move to next location
50                const fuelCost: number = Math.abs(locations[currentLocation] - locations[nextLocation]);
51              
52                // Recursively count routes from next location with reduced fuel
53                routeCount = (routeCount + dfs(nextLocation, remainingFuel - fuelCost)) % MODULO;
54            }
55        }
56      
57        // Memoize and return the result
58        memo[currentLocation][remainingFuel] = routeCount;
59        return routeCount;
60    };
61  
62    // Start the search from initial location with full fuel
63    return dfs(start, fuel);
64}
65

Time and Space Complexity

The time complexity is O(n^2 × m), where n is the size of the array locations and m is the value of fuel.

Time Complexity Analysis:

  • The recursive function dfs(i, k) has at most n × m unique states, where i ranges from 0 to n-1 (representing different locations) and k ranges from 0 to m (representing remaining fuel).
  • Due to the @cache decorator, each state is computed only once.
  • For each state (i, k), the function iterates through all n locations to find possible next destinations, taking O(n) time.
  • Therefore, the total time complexity is O(n × m) × O(n) = O(n^2 × m).

The space complexity is O(n × m).

Space Complexity Analysis:

  • The @cache decorator stores the results of all computed states, which requires O(n × m) space to store all possible combinations of location index and remaining fuel.
  • The recursion depth in the worst case could be O(m) (when we use 1 unit of fuel at each step), but this is dominated by the cache storage.
  • Therefore, the total space complexity is O(n × m).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Early Termination Logic

The Pitfall: A common mistake is to terminate early when remaining_fuel < abs(locations[current_city] - locations[finish]), thinking no valid routes exist. However, this optimization is actually incorrect for this problem.

Why It's Wrong: The finish city might not be the farthest city from our current position. Even if we can't reach the finish directly, we might be able to reach it through intermediate cities that are closer to us.

Example:

  • Locations: [1, 5, 3], start=0 (position 1), finish=2 (position 3), fuel=3
  • From city 0, direct distance to finish is |1-3|=2, which is feasible
  • But if we had fuel=1, direct route is impossible (needs 2 fuel)
  • However, we could still go 0→1 (cost |1-5|=4), which exceeds our fuel
  • The early termination would incorrectly return 0 for some valid paths through closer intermediate cities

The Fix: Remove the early termination check entirely or make it more sophisticated:

@cache
def dfs(current_city: int, remaining_fuel: int) -> int:
    # Remove the incorrect early termination
    # if remaining_fuel < abs(locations[current_city] - locations[finish]):
    #     return 0
  
    route_count = 1 if current_city == finish else 0
  
    for next_city in range(len(locations)):
        if next_city != current_city:
            fuel_cost = abs(locations[current_city] - locations[next_city])
            # Only skip if we don't have enough fuel for this specific move
            if fuel_cost <= remaining_fuel:
                route_count = (route_count + dfs(next_city, remaining_fuel - fuel_cost)) % MOD
  
    return route_count

2. Forgetting to Check Fuel Before Making a Move

The Pitfall: Calling dfs(next_city, remaining_fuel - fuel_cost) without first checking if fuel_cost <= remaining_fuel, which could lead to exploring states with negative fuel.

Why It's Wrong: The problem explicitly states you cannot make a move if it would cause your fuel to become negative. Exploring negative fuel states violates the problem constraints.

The Fix: Always validate fuel sufficiency before making a recursive call:

for next_city in range(len(locations)):
    if next_city != current_city:
        fuel_cost = abs(locations[current_city] - locations[next_city])
        if fuel_cost <= remaining_fuel:  # Check fuel sufficiency first
            route_count = (route_count + dfs(next_city, remaining_fuel - fuel_cost)) % MOD

3. Forgetting the Modulo Operation

The Pitfall: Not applying modulo at each addition step, only applying it at the final return:

# Wrong approach
route_count = 1 if current_city == finish else 0
for next_city in range(len(locations)):
    if next_city != current_city and fuel_cost <= remaining_fuel:
        route_count += dfs(next_city, remaining_fuel - fuel_cost)
return route_count % MOD  # Only modulo at the end

Why It's Wrong: Integer overflow can occur during intermediate calculations before the final modulo operation, especially in languages with fixed integer sizes. Even in Python with arbitrary precision integers, it's good practice to apply modulo consistently.

The Fix: Apply modulo after each addition to keep numbers manageable:

route_count = (route_count + dfs(next_city, remaining_fuel - fuel_cost)) % MOD
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What data structure does Breadth-first search typically uses to store intermediate states?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More