Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

2297. Jump Game VIII - Leetcode Solution

Problem Description

The Jump Game VIII problem builds on the classic Jump Game series. You are given an array nums of length n, where each element nums[i] represents the maximum number of steps you can jump forward from that position. In this version, there may also be an array cost of the same length, where cost[i] is the cost of jumping from index i. Your goal is to determine the minimum total cost to reach the last index of the array, starting from the first index (index 0).

  • You can jump from index i to any index j such that i < j ≤ i + nums[i].
  • The cost incurred is cost[j] for landing at index j (sometimes the cost may be paid at the source, depending on the exact problem variant; here, we assume the cost is at the destination).
  • Return the minimal total cost to reach the last index. If it is not possible to reach the end, return -1.
  • Constraints: 1 ≤ n ≤ 10^5, 0 ≤ nums[i] ≤ n, 0 ≤ cost[i] ≤ 10^9.

Thought Process

At first glance, the problem resembles the classic Jump Game, but the added cost array means we must optimize for minimal cost, not just reachability. A brute-force approach would be to try every possible jump from every index and keep track of the minimum cost to reach each position. However, this would be very slow for large arrays.

The problem now resembles the "shortest path" problem in a graph, where each index is a node, and you can jump from node i to nodes j with a directed edge of cost cost[j]. The goal is to find the minimum cost path from node 0 to node n-1. This suggests using algorithms like Dijkstra's algorithm, which is efficient for finding shortest paths in graphs with non-negative edge weights.

The main challenge is efficiently processing possible jumps, as each node can have up to nums[i] outgoing edges. We need a way to avoid redundant calculations and keep the algorithm efficient.

Solution Approach

We can model the problem as a graph and use a priority queue (min-heap) to always process the currently reachable node with the lowest total cost so far. Here is a step-by-step breakdown:

  1. Initialization:
    • Create an array min_cost of size n to store the minimum cost to reach each index. Initialize all values to infinity, except min_cost[0] = cost[0] (since we start at index 0 and pay its cost).
    • Create a min-heap (priority queue) and push (min_cost[0], 0) onto it.
  2. Processing:
    • While the heap is not empty, pop the node with the smallest current cost.
    • For each possible jump from the current index i (i.e., for each j in [i+1, min(i+nums[i], n-1)]):
      • If min_cost[j] > curr_cost + cost[j], update min_cost[j] and push (min_cost[j], j) onto the heap.
  3. Termination:
    • When the heap is empty, check min_cost[n-1]. If it is still infinity, return -1 (unreachable); otherwise, return min_cost[n-1].

This approach is efficient because the heap ensures we always expand the lowest-cost paths first, and we never revisit a node with a higher cost than previously found.

Example Walkthrough

Suppose nums = [2,3,1,1,4] and cost = [1,2,4,1,3].

  • Start at index 0: min_cost[0] = 1.
  • From index 0, you can jump to indices 1 and 2:
    • Jump to 1: cost = 1 (start) + 2 = 3 → min_cost[1] = 3
    • Jump to 2: cost = 1 + 4 = 5 → min_cost[2] = 5
  • Process index 1 (lowest cost so far): from 1, can jump to 2, 3, 4:
    • Jump to 2: cost = 3 + 4 = 7 (but min_cost[2] is already 5, so skip)
    • Jump to 3: cost = 3 + 1 = 4 → min_cost[3] = 4
    • Jump to 4: cost = 3 + 3 = 6 → min_cost[4] = 6
  • Process index 3: from 3, can jump to 4:
    • Jump to 4: cost = 4 + 3 = 7 (but min_cost[4] is already 6, so skip)
  • Process index 2: can jump to 3:
    • Jump to 3: cost = 5 + 1 = 6 (but min_cost[3] is already 4, so skip)
  • Process index 4: reached last index with cost 6.

The answer is 6.

Time and Space Complexity

  • Brute-force: For each index, try all possible jumps, leading to O(n^2) time in the worst case. This is not feasible for large n.
  • Optimized (Dijkstra's):
    • Each node is pushed onto the heap at most once per unique minimal cost found, and each edge (jump) is considered once.
    • There are O(n) nodes and up to O(n^2) edges, but in practice, the heap ensures we do not process redundant paths.
    • With careful implementation, the time complexity is O(n log n) for reasonable nums[i], but in the worst case (if all nums[i] = n), it can approach O(n^2).
    • Space complexity is O(n) for the min_cost array and heap.

Summary

The Jump Game VIII problem extends the classic jump game by adding costs, turning it into a shortest path problem on a graph. By modeling the array as a graph and using Dijkstra's algorithm with a min-heap, we efficiently compute the minimum cost to reach the last index. The solution leverages greedy expansion and efficient updates to guarantee the minimum cost path, making it both elegant and practical for large input sizes.

Code Implementation

import heapq

def minCostJump(nums, cost):
    n = len(nums)
    min_cost = [float('inf')] * n
    min_cost[0] = cost[0]
    heap = [(cost[0], 0)]

    while heap:
        curr_cost, i = heapq.heappop(heap)
        if i == n - 1:
            return curr_cost
        if curr_cost > min_cost[i]:
            continue
        for j in range(i + 1, min(i + nums[i] + 1, n)):
            new_cost = curr_cost + cost[j]
            if new_cost < min_cost[j]:
                min_cost[j] = new_cost
                heapq.heappush(heap, (new_cost, j))
    return -1
      
#include <vector>
#include <queue>
#include <limits>
using namespace std;

int minCostJump(vector<int>& nums, vector<int>& cost) {
    int n = nums.size();
    vector<long long> min_cost(n, LLONG_MAX);
    min_cost[0] = cost[0];
    priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> pq;
    pq.push({cost[0], 0});

    while (!pq.empty()) {
        auto [curr_cost, i] = pq.top(); pq.pop();
        if (i == n - 1) return curr_cost;
        if (curr_cost > min_cost[i]) continue;
        for (int j = i + 1; j <= min(i + nums[i], n - 1); ++j) {
            long long new_cost = curr_cost + cost[j];
            if (new_cost < min_cost[j]) {
                min_cost[j] = new_cost;
                pq.push({new_cost, j});
            }
        }
    }
    return -1;
}
      
import java.util.*;

public class Solution {
    public int minCostJump(int[] nums, int[] cost) {
        int n = nums.length;
        long[] minCost = new long[n];
        Arrays.fill(minCost, Long.MAX_VALUE);
        minCost[0] = cost[0];
        PriorityQueue<long[]> pq = new PriorityQueue<>(Comparator.comparingLong(a -> a[0]));
        pq.offer(new long[]{cost[0], 0});

        while (!pq.isEmpty()) {
            long[] curr = pq.poll();
            long currCost = curr[0];
            int i = (int)curr[1];
            if (i == n - 1) return (int)currCost;
            if (currCost > minCost[i]) continue;
            for (int j = i + 1; j <= Math.min(i + nums[i], n - 1); ++j) {
                long newCost = currCost + cost[j];
                if (newCost < minCost[j]) {
                    minCost[j] = newCost;
                    pq.offer(new long[]{newCost, j});
                }
            }
        }
        return -1;
    }
}
      
class MinHeap {
    constructor() {
        this.heap = [];
    }
    push(val) {
        this.heap.push(val);
        this._bubbleUp(this.heap.length - 1);
    }
    pop() {
        if (this.heap.length === 1) return this.heap.pop();
        const top = this.heap[0];
        this.heap[0] = this.heap.pop();
        this._bubbleDown(0);
        return top;
    }
    _bubbleUp(idx) {
        while (idx > 0) {
            const parent = Math.floor((idx - 1) / 2);
            if (this.heap[parent][0] <= this.heap[idx][0]) break;
            [this.heap[parent], this.heap[idx]] = [this.heap[idx], this.heap[parent]];
            idx = parent;
        }
    }
    _bubbleDown(idx) {
        const n = this.heap.length;
        while (true) {
            let smallest = idx;
            const left = 2 * idx + 1, right = 2 * idx + 2;
            if (left < n && this.heap[left][0] < this.heap[smallest][0]) smallest = left;
            if (right < n && this.heap[right][0] < this.heap[smallest][0]) smallest = right;
            if (smallest === idx) break;
            [this.heap[smallest], this.heap[idx]] = [this.heap[idx], this.heap[smallest]];
            idx = smallest;
        }
    }
    isEmpty() {
        return this.heap.length === 0;
    }
}

function minCostJump(nums, cost) {
    const n = nums.length;
    const minCost = Array(n).fill(Infinity);
    minCost[0] = cost[0];
    const heap = new MinHeap();
    heap.push([cost[0], 0]);
    while (!heap.isEmpty()) {
        const [currCost, i] = heap.pop();
        if (i === n - 1) return currCost;
        if (currCost > minCost[i]) continue;
        for (let j = i + 1; j <= Math.min(i + nums[i], n - 1); ++j) {
            const newCost = currCost + cost[j];
            if (newCost < minCost[j]) {
                minCost[j] = newCost;
                heap.push([newCost, j]);
            }
        }
    }
    return -1;
}