Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

656. Coin Path - Leetcode Solution

Problem Description

The Coin Path problem asks you to find the cheapest path from the start to the end of an array of coins, where each coin has a cost. You are given:

  • An array A of length n, where A[i] is the cost to step on the i-th position. If A[i] == -1, that position is blocked and cannot be stepped on.
  • An integer B (1 ≤ B ≤ n), representing the maximum number of steps you can jump forward from your current position.

Starting at index 0, your goal is to reach index n-1 (the end) with the minimum total cost. At each step, you can jump forward at most B positions, but only onto unblocked positions (A[j] != -1). You must return the path as a list of 1-based indices. If there are multiple paths with the same minimum cost, return the lexicographically smallest one. If it's impossible to reach the end, return an empty list.

Constraints:

  • Each position can be used at most once.
  • There is at most one valid solution.

Thought Process

This problem looks a bit like a mix of the minimum path sum and jump game problems. The most basic approach would be to try all possible paths from the start, keeping track of their costs, and picking the cheapest one. However, this brute-force way would be extremely slow for large arrays because the number of possible paths grows exponentially.

We need to optimize. Since each jump is limited to B steps and some positions are blocked, we can use dynamic programming to store the minimum cost to reach each position. By working backwards (from the end to the start), or forwards (from the start to the end), we can efficiently compute the minimum cost to reach the end from each position. Additionally, to reconstruct the path, we keep track of where each minimum cost comes from.

The lexicographical order requirement means that when two paths have the same cost, we should prefer the one that steps to the smallest index next. This can be handled by always updating the path if a cheaper or equally cheap path is found via a smaller index.

Solution Approach

We solve this problem using dynamic programming with path reconstruction. Here's a step-by-step breakdown:

  1. Initialize arrays:
    • Create an array dp of size n where dp[i] represents the minimum cost to reach the end from position i.
    • Initialize dp[n-1] to A[n-1] (the cost of the last position), unless it's blocked (-1), in which case it's unreachable.
    • Create a next_index array to help reconstruct the path. next_index[i] stores the next position to jump to from i in the optimal path.
  2. Fill dp from end to start:
    • For each position i from n-2 down to 0:
    • If A[i] == -1, skip (blocked).
    • For each possible jump j from i+1 to min(i+B, n-1):
      • If dp[j] is not inf and A[j] != -1, consider dp[j] + A[i] as a candidate cost.
      • Keep the minimum such cost and record j as the next step if it's cheaper or lexicographically smaller.
  3. Reconstruct the path:
    • If dp[0] is inf, return an empty list (no path).
    • Otherwise, start from 0 and use next_index to follow the path to the end, collecting 1-based indices.

This approach ensures we always pick the cheapest path, and in case of ties, the lexicographically smallest path.

Example Walkthrough

Example:
Input: A = [1,2,4,-1,2], B = 2
Output: [1,3,5]

  1. Initialization:
    • n = 5
    • dp = [inf, inf, inf, inf, 2] (since A[4]=2 is the last position)
    • next_index = [-1, -1, -1, -1, -1]
  2. Fill dp:
    • i = 3: A[3] = -1 → blocked, skip.
    • i = 2: Can jump to 3 or 4. 3 is blocked, 4 is end (dp[4]=2), so dp[2]=4+2=6, next_index[2]=4.
    • i = 1: Can jump to 2 or 3. 2 has dp[2]=6, so dp[1]=2+6=8, next_index[1]=2.
    • i = 0: Can jump to 1 or 2. 1 has dp[1]=8, 2 has dp[2]=6. 2 is better: dp[0]=1+6=7, next_index[0]=2.
  3. Path reconstruction:
    • Start at 0 (1-based: 1), next is 2 (1-based: 3), next is 4 (1-based: 5).
    • So, path is [1,3,5].

Time and Space Complexity

Brute-force approach:
For each position, try all possible paths recursively. The number of paths can be exponential in n, so the time complexity is O(B^n), which is not feasible for large n.

Dynamic programming approach:
For each position i, we look at at most B next positions. So, total work is O(nB).
Space complexity is O(n) for the dp and next_index arrays.

Summary

The Coin Path problem is a classic example of using dynamic programming to efficiently find a minimum-cost path with constraints. By breaking the problem into subproblems (minimum cost to reach the end from each position), and carefully reconstructing the path, we can solve it in O(nB) time. The lexicographical constraint is handled by always preferring smaller next indices in case of ties. This approach is both efficient and elegant, and illustrates the power of dynamic programming for path-finding problems.

Code Implementation

class Solution:
    def cheapestJump(self, A, B):
        n = len(A)
        INF = float('inf')
        dp = [INF] * n
        nxt = [-1] * n

        if A[-1] != -1:
            dp[-1] = A[-1]
        # Fill dp from end to start
        for i in range(n - 2, -1, -1):
            if A[i] == -1:
                continue
            for j in range(i + 1, min(i + B + 1, n)):
                if dp[j] != INF:
                    if dp[j] + A[i] < dp[i]:
                        dp[i] = dp[j] + A[i]
                        nxt[i] = j
        # If unreachable
        if dp[0] == INF:
            return []
        # Reconstruct path
        path = []
        idx = 0
        while idx != -1:
            path.append(idx + 1)
            idx = nxt[idx]
        return path
      
class Solution {
public:
    vector<int> cheapestJump(vector<int>& A, int B) {
        int n = A.size();
        const int INF = 1e9 + 7;
        vector<int> dp(n, INF), next(n, -1);
        if (A[n-1] != -1) dp[n-1] = A[n-1];
        for (int i = n-2; i >= 0; --i) {
            if (A[i] == -1) continue;
            for (int j = i+1; j <= min(n-1, i+B); ++j) {
                if (dp[j] != INF) {
                    if (dp[j] + A[i] < dp[i]) {
                        dp[i] = dp[j] + A[i];
                        next[i] = j;
                    }
                }
            }
        }
        if (dp[0] == INF) return {};
        vector<int> path;
        int idx = 0;
        while (idx != -1) {
            path.push_back(idx + 1);
            idx = next[idx];
        }
        return path;
    }
};
      
class Solution {
    public List<Integer> cheapestJump(int[] A, int B) {
        int n = A.length;
        int INF = Integer.MAX_VALUE / 2;
        int[] dp = new int[n];
        int[] next = new int[n];
        Arrays.fill(dp, INF);
        Arrays.fill(next, -1);
        if (A[n-1] != -1) dp[n-1] = A[n-1];
        for (int i = n-2; i >= 0; --i) {
            if (A[i] == -1) continue;
            for (int j = i+1; j <= Math.min(i+B, n-1); ++j) {
                if (dp[j] != INF) {
                    if (dp[j] + A[i] < dp[i]) {
                        dp[i] = dp[j] + A[i];
                        next[i] = j;
                    }
                }
            }
        }
        List<Integer> res = new ArrayList<>();
        if (dp[0] == INF) return res;
        int idx = 0;
        while (idx != -1) {
            res.add(idx + 1);
            idx = next[idx];
        }
        return res;
    }
}
      
var cheapestJump = function(A, B) {
    const n = A.length;
    const INF = Number.MAX_SAFE_INTEGER;
    let dp = new Array(n).fill(INF);
    let next = new Array(n).fill(-1);
    if (A[n-1] !== -1) dp[n-1] = A[n-1];
    for (let i = n-2; i >= 0; --i) {
        if (A[i] === -1) continue;
        for (let j = i+1; j <= Math.min(i+B, n-1); ++j) {
            if (dp[j] !== INF) {
                if (dp[j] + A[i] < dp[i]) {
                    dp[i] = dp[j] + A[i];
                    next[i] = j;
                }
            }
        }
    }
    if (dp[0] === INF) return [];
    let path = [];
    let idx = 0;
    while (idx !== -1) {
        path.push(idx + 1);
        idx = next[idx];
    }
    return path;
};