Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1575. Count All Possible Routes - Leetcode Solution

Problem Description

The "Count All Possible Routes" problem asks you to determine the number of different routes you can take from a start city to a finish city, given a list of city locations and a limited amount of fuel.

Each route is a sequence of moves between cities, where traveling from city i to city j costs abs(locations[i] - locations[j]) units of fuel. You may visit the same city multiple times, including the start and finish cities, as long as you have enough fuel for each move. The goal is to count the total number of possible routes from start to finish (including routes that visit the finish city more than once), using at most the given amount of fuel.

Key constraints:

  • Each move must not exceed the remaining fuel.
  • You can revisit cities, including the start and finish cities.
  • The answer may be large, so return it modulo 10^9 + 7.

Thought Process

At first glance, this problem seems to require exploring all possible paths from start to finish within the fuel limit. A brute-force approach would be to recursively try every possible move from each city, keeping track of the remaining fuel, and counting every time we reach the finish city.

However, this naive approach quickly becomes infeasible: the number of possible paths grows exponentially with the number of cities and the amount of fuel. Many of these paths revisit the same city with the same remaining fuel, leading to repeated calculations.

To optimize, we notice that the state of our problem is entirely determined by our current city and the amount of fuel left. If we have already calculated the number of routes from a given city with a given amount of fuel, we can store (memoize) that result and reuse it later, dramatically reducing redundant work.

Solution Approach

We'll use a recursive depth-first search (DFS) with memoization (also known as dynamic programming with top-down recursion) to efficiently count all possible routes. Here's how the algorithm works:

  1. Define the State: The state is defined by the current city curr and the remaining fuel.
  2. Base Case: If the fuel is less than zero, return 0 (no valid route).
  3. Count Routes:
    • If the current city is the finish city, we count this as a valid route (even if we have fuel left).
    • Try moving to every other city (except the current one), subtracting the required fuel for the move. For each possible next city, recursively count the number of routes from there with the updated fuel.
  4. Memoization: Store the result for each (city, fuel) pair in a cache (e.g., a 2D array or hash map) to avoid recalculating it.
  5. Return the Answer: The total number of routes from the initial state is the answer, modulo 10^9 + 7.

This approach ensures that each unique (city, fuel) combination is only computed once, leading to significant performance gains.

Example Walkthrough

Sample Input:
locations = [2, 3, 6, 8, 4]
start = 1 (city at position 3)
finish = 3 (city at position 8)
fuel = 5

Step-by-step trace:

  • We start at city 1 with 5 units of fuel.
  • Possible moves from city 1:
    • To city 0: cost = 1 (fuel left: 4)
    • To city 2: cost = 3 (fuel left: 2)
    • To city 3: cost = 5 (fuel left: 0)
    • To city 4: cost = 1 (fuel left: 4)
  • For each move, recursively explore all further moves, always checking if we reach city 3 (the finish).
  • Whenever we land at city 3, we count one valid route (even if fuel is not zero).
  • Memoization ensures that, for example, if we reach city 2 with 2 units of fuel from different paths, we only compute the routes from that state once.
  • By the end, we sum all possible valid ways to reach city 3, considering all possible sequences and fuel usage.

For this example, the answer is 4: there are 4 different possible routes from city 1 to city 3 with at most 5 fuel.

Time and Space Complexity

Brute-force Approach:
The brute-force method explores all possible paths, leading to exponential time complexity: O(N^F), where N is the number of cities and F is the fuel. This is not feasible for large inputs.

Optimized Approach (DP with Memoization):

  • Time Complexity: O(N * F * N). For each of the N cities and up to F+1 fuel values, we may try up to N-1 possible next moves. In practice, since we memoize, the number of unique states is O(N * F), and for each state, we iterate over N cities.
  • Space Complexity: O(N * F), due to the memoization cache storing results for each (city, fuel) pair.

This makes the solution efficient enough for the problem's constraints.

Summary

The "Count All Possible Routes" problem is a classic example of dynamic programming with overlapping subproblems, where naive recursion would be far too slow. By recognizing that the state is determined by (current city, remaining fuel), we can use memoization to efficiently count all possible valid routes. The solution demonstrates the power of DP in reducing exponential problems to polynomial time, and highlights the importance of caching repeated computations.

Code Implementation

from functools import lru_cache

class Solution:
    def countRoutes(self, locations, start, finish, fuel):
        MOD = 10**9 + 7
        n = len(locations)

        @lru_cache(None)
        def dfs(curr, fuel_left):
            if fuel_left < 0:
                return 0
            res = 1 if curr == finish else 0
            for next_city in range(n):
                if next_city != curr:
                    cost = abs(locations[curr] - locations[next_city])
                    if fuel_left - cost >= 0:
                        res = (res + dfs(next_city, fuel_left - cost)) % MOD
            return res

        return dfs(start, fuel)
      
class Solution {
public:
    int countRoutes(vector<int>& locations, int start, int finish, int fuel) {
        const int MOD = 1e9 + 7;
        int n = locations.size();
        vector<vector<int>> dp(n, vector<int>(fuel + 1, -1));
        
        function<int(int, int)> dfs = [&](int curr, int fuel_left) {
            if (fuel_left < 0) return 0;
            if (dp[curr][fuel_left] != -1) return dp[curr][fuel_left];
            int res = (curr == finish) ? 1 : 0;
            for (int next = 0; next < n; ++next) {
                if (next != curr) {
                    int cost = abs(locations[curr] - locations[next]);
                    if (fuel_left - cost >= 0) {
                        res = (res + dfs(next, fuel_left - cost)) % MOD;
                    }
                }
            }
            return dp[curr][fuel_left] = res;
        };
        
        return dfs(start, fuel);
    }
};
      
class Solution {
    private static final int MOD = 1_000_000_007;
    int[][] dp;
    int[] locations;
    int finish;

    public int countRoutes(int[] locations, int start, int finish, int fuel) {
        int n = locations.length;
        this.locations = locations;
        this.finish = finish;
        dp = new int[n][fuel + 1];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j <= fuel; j++) {
                dp[i][j] = -1;
            }
        }
        return dfs(start, fuel);
    }

    private int dfs(int curr, int fuel_left) {
        if (fuel_left < 0) return 0;
        if (dp[curr][fuel_left] != -1) return dp[curr][fuel_left];
        int res = (curr == finish) ? 1 : 0;
        for (int next = 0; next < locations.length; next++) {
            if (next != curr) {
                int cost = Math.abs(locations[curr] - locations[next]);
                if (fuel_left - cost >= 0) {
                    res = (res + dfs(next, fuel_left - cost)) % MOD;
                }
            }
        }
        dp[curr][fuel_left] = res;
        return res;
    }
}
      
var countRoutes = function(locations, start, finish, fuel) {
    const MOD = 1e9 + 7;
    const n = locations.length;
    const memo = Array.from({length: n}, () => Array(fuel + 1).fill(-1));
    
    function dfs(curr, fuel_left) {
        if (fuel_left < 0) return 0;
        if (memo[curr][fuel_left] !== -1) return memo[curr][fuel_left];
        let res = (curr === finish) ? 1 : 0;
        for (let next = 0; next < n; ++next) {
            if (next !== curr) {
                const cost = Math.abs(locations[curr] - locations[next]);
                if (fuel_left - cost >= 0) {
                    res = (res + dfs(next, fuel_left - cost)) % MOD;
                }
            }
        }
        memo[curr][fuel_left] = res;
        return res;
    }
    
    return dfs(start, fuel);
};