Facebook Pixel

1340. Jump Game V

Problem Description

You are given an array of integers arr and an integer d representing the maximum jump distance. You need to find the maximum number of indices you can visit by jumping through the array.

From any index i, you can jump to another index j if the following conditions are met:

  1. Distance constraint: The jump distance must be within d. This means:

    • You can jump forward to i + x where 0 < x ≤ d and i + x < arr.length
    • You can jump backward to i - x where 0 < x ≤ d and i - x ≥ 0
  2. Height constraint: You can only jump from a higher value to a lower value. Specifically:

    • arr[i] > arr[j] (the destination must have a smaller value)
    • arr[i] > arr[k] for all indices k between i and j (all intermediate values must also be smaller than the starting value)

You can start from any index in the array and begin jumping. Each index can be visited at most once in a single path. The goal is to find the maximum number of indices you can visit in a single sequence of jumps.

For example, if arr = [6,4,14,6,8,13,9,7,10,6,12] and d = 2:

  • From index 2 (value 14), you could jump to index 1 (value 4) or index 3 (value 6) or index 4 (value 8), since they are within distance 2 and all have smaller values than 14
  • From index 10 (value 12), you could jump to index 9 (value 6) since it's within distance 2 and has a smaller value

The solution uses dynamic programming with memoization to explore all possible jumping paths from each starting position and returns the maximum length path found.

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

Intuition

The key insight is that this problem has an optimal substructure property - if we know the maximum number of jumps we can make starting from each position, we can build up the solution for other positions.

Think of it this way: when standing at index i, we need to find the longest path starting from this position. This path consists of:

  1. The current index (count = 1)
  2. Plus the longest path we can achieve from any valid jump destination

Since we can only jump to lower values (with all intermediate values also being lower), we're essentially creating a directed acyclic graph (DAG) where edges point from higher values to reachable lower values. This prevents cycles and makes the problem suitable for dynamic programming.

The recursive nature becomes clear: maxJumps(i) = 1 + max(maxJumps(j)) for all valid jump destinations j from i.

We use memoization (@cache) because multiple starting positions might jump to the same intermediate position. For instance, if both index 2 and index 5 can jump to index 3, we don't want to recalculate the maximum jumps from index 3 twice.

The algorithm explores two directions from each position:

  • Left direction: Check positions i-1, i-2, ..., max(0, i-d) and stop when we hit a value that's not smaller than arr[i] (violating the height constraint)
  • Right direction: Check positions i+1, i+2, ..., min(n-1, i+d) and stop when we hit a value that's not smaller than arr[i]

The early stopping is crucial - once we encounter a value that's greater than or equal to arr[i], we know we can't jump over it to reach further positions, so we break out of that direction.

Finally, since we can start from any position, we try all possible starting points and return the maximum path length found.

Learn more about Dynamic Programming and Sorting patterns.

Solution Approach

The solution implements a top-down dynamic programming approach with memoization using depth-first search (DFS).

Core Implementation:

  1. DFS Function with Memoization: The dfs(i) function calculates the maximum number of indices that can be visited starting from index i. The @cache decorator automatically memoizes the results to avoid redundant calculations.

  2. Base Case: Each position can visit at least itself, so we initialize ans = 1.

  3. Exploring Left Jumps:

    for j in range(i - 1, -1, -1):
        if i - j > d or arr[j] >= arr[i]:
            break
        ans = max(ans, 1 + dfs(j))
    • Iterate backwards from i-1 to 0
    • Check two stopping conditions:
      • Distance exceeded: i - j > d
      • Height constraint violated: arr[j] >= arr[i]
    • If both conditions are satisfied, recursively calculate dfs(j) and update the maximum
  4. Exploring Right Jumps:

    for j in range(i + 1, n):
        if j - i > d or arr[j] >= arr[i]:
            break
        ans = max(ans, 1 + dfs(j))
    • Iterate forwards from i+1 to n-1
    • Apply the same stopping conditions for distance and height
    • Update the maximum with 1 + dfs(j) if valid
  5. Early Termination Optimization: The loops break immediately when encountering a value greater than or equal to arr[i]. This is valid because all subsequent positions in that direction would be unreachable (blocked by the taller or equal element).

  6. Global Maximum: Since we can start from any index, we compute dfs(i) for all positions and return the maximum:

    return max(dfs(i) for i in range(n))

Time Complexity: O(n * d) where n is the array length. Each position is computed once (due to memoization), and for each position, we check at most 2d other positions.

Space Complexity: O(n) for the memoization cache storing results for each index, plus O(n) for the recursion stack in the worst case.

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: arr = [7, 6, 5, 4, 3, 2], d = 2

Let's trace through the DFS calls starting from different positions:

Starting from index 0 (value 7):

  • From index 0, we can jump within distance 2
  • Check left: No positions to the left
  • Check right:
    • Index 1 (value 6): Valid! 6 < 7, distance = 1 ≤ 2
    • Index 2 (value 5): Valid! 5 < 7, distance = 2 ≤ 2
  • Now we recursively calculate:
    • dfs(1): From index 1 (value 6)
      • Can jump to index 2 (5) or index 3 (4)
      • dfs(2) returns 3 (can visit 2→3→4)
      • dfs(3) returns 2 (can visit 3→4)
      • So dfs(1) = 1 + max(3, 2) = 4
    • dfs(2): From index 2 (value 5)
      • Can jump to index 3 (4) or index 4 (3)
      • dfs(3) returns 2 (already computed)
      • dfs(4) returns 1 (can only visit 4→5)
      • So dfs(2) = 1 + max(2, 1) = 3
  • Therefore, dfs(0) = 1 + max(4, 3) = 5

Starting from index 3 (value 4):

  • Check left:
    • Index 2 (value 5): Can't jump (5 ≥ 4)
    • Stop checking left
  • Check right:
    • Index 4 (value 3): Valid! 3 < 4
    • Index 5 (value 2): Valid! 2 < 4
  • Calculate:
    • dfs(4) = 2 (can visit 4→5)
    • dfs(5) = 1 (no valid jumps)
  • So dfs(3) = 1 + max(2, 1) = 3

The memoization ensures we don't recalculate dfs(3), dfs(4), etc. when they're needed multiple times.

Final Result: The maximum path is 5, achieved by starting at index 0 and following the path: 0→1→2→3→4 (visiting indices with values 7→6→5→4→3).

This example demonstrates:

  • How the algorithm explores both directions from each position
  • The importance of the height constraint (can only jump to lower values)
  • How memoization prevents redundant calculations
  • The recursive nature of building up solutions from subproblems

Solution Implementation

1from typing import List
2from functools import cache
3
4class Solution:
5    def maxJumps(self, arr: List[int], d: int) -> int:
6        """
7        Find the maximum number of indices you can visit starting from any index.
8        You can jump from index i to index j if:
9        - i - d <= j <= i + d (j != i)
10        - arr[k] < arr[i] for all k between i and j (exclusive)
11      
12        Args:
13            arr: List of integers representing heights
14            d: Maximum jump distance
15          
16        Returns:
17            Maximum number of indices that can be visited
18        """
19        @cache
20        def dfs(index: int) -> int:
21            """
22            Perform depth-first search to find max jumps from given index.
23          
24            Args:
25                index: Current position in the array
26              
27            Returns:
28                Maximum number of indices reachable from current index
29            """
30            # Start with 1 (counting current index)
31            max_jumps = 1
32          
33            # Try jumping to the left (indices before current)
34            for next_index in range(index - 1, -1, -1):
35                # Stop if distance exceeds d or we hit a taller/equal element
36                if index - next_index > d or arr[next_index] >= arr[index]:
37                    break
38                # Update max jumps if jumping to next_index gives better result
39                max_jumps = max(max_jumps, 1 + dfs(next_index))
40          
41            # Try jumping to the right (indices after current)
42            for next_index in range(index + 1, array_length):
43                # Stop if distance exceeds d or we hit a taller/equal element
44                if next_index - index > d or arr[next_index] >= arr[index]:
45                    break
46                # Update max jumps if jumping to next_index gives better result
47                max_jumps = max(max_jumps, 1 + dfs(next_index))
48          
49            return max_jumps
50      
51        # Store array length for efficiency
52        array_length = len(arr)
53      
54        # Find maximum jumps starting from each index
55        return max(dfs(i) for i in range(array_length))
56
1class Solution {
2    // Instance variables for array dimensions and memoization
3    private int arrayLength;
4    private int maxDistance;
5    private int[] heights;
6    private Integer[] memo;  // Memoization array to store computed results
7
8    /**
9     * Finds the maximum number of indices you can visit in the array.
10     * You can jump from index i to index j if:
11     * 1. i != j
12     * 2. |i - j| <= d
13     * 3. All elements between i and j are strictly less than arr[i]
14     * 
15     * @param arr Array of heights
16     * @param d Maximum jump distance
17     * @return Maximum number of indices that can be visited
18     */
19    public int maxJumps(int[] arr, int d) {
20        // Initialize instance variables
21        arrayLength = arr.length;
22        this.maxDistance = d;
23        this.heights = arr;
24        memo = new Integer[arrayLength];
25      
26        int maxVisited = 1;
27      
28        // Try starting from each index and find the maximum path
29        for (int startIndex = 0; startIndex < arrayLength; startIndex++) {
30            maxVisited = Math.max(maxVisited, dfs(startIndex));
31        }
32      
33        return maxVisited;
34    }
35
36    /**
37     * Depth-first search to find the maximum number of jumps from index i.
38     * Uses memoization to avoid recomputation.
39     * 
40     * @param currentIndex Current position in the array
41     * @return Maximum number of indices reachable from currentIndex (including itself)
42     */
43    private int dfs(int currentIndex) {
44        // Return cached result if already computed
45        if (memo[currentIndex] != null) {
46            return memo[currentIndex];
47        }
48      
49        // Start with 1 (current position counts as visited)
50        int maxJumpsFromHere = 1;
51      
52        // Try jumping to the left
53        for (int targetIndex = currentIndex - 1; targetIndex >= 0; targetIndex--) {
54            // Stop if distance exceeds limit or target is not lower
55            if (currentIndex - targetIndex > maxDistance || heights[targetIndex] >= heights[currentIndex]) {
56                break;
57            }
58            // Update maximum if jumping to targetIndex yields better result
59            maxJumpsFromHere = Math.max(maxJumpsFromHere, 1 + dfs(targetIndex));
60        }
61      
62        // Try jumping to the right
63        for (int targetIndex = currentIndex + 1; targetIndex < arrayLength; targetIndex++) {
64            // Stop if distance exceeds limit or target is not lower
65            if (targetIndex - currentIndex > maxDistance || heights[targetIndex] >= heights[currentIndex]) {
66                break;
67            }
68            // Update maximum if jumping to targetIndex yields better result
69            maxJumpsFromHere = Math.max(maxJumpsFromHere, 1 + dfs(targetIndex));
70        }
71      
72        // Cache and return the result
73        memo[currentIndex] = maxJumpsFromHere;
74        return maxJumpsFromHere;
75    }
76}
77
1class Solution {
2public:
3    int maxJumps(vector<int>& arr, int d) {
4        int n = arr.size();
5      
6        // dp[i] stores the maximum number of indices that can be visited starting from index i
7        vector<int> dp(n, 0);
8      
9        // DFS with memoization to calculate maximum jumps from index i
10        function<int(int)> dfs = [&](int index) -> int {
11            // Return cached result if already computed
12            if (dp[index] != 0) {
13                return dp[index];
14            }
15          
16            // At minimum, we can visit the current index itself
17            int maxVisits = 1;
18          
19            // Try jumping to the left (indices smaller than current)
20            for (int j = index - 1; j >= 0; --j) {
21                // Stop if distance exceeds d or we encounter a taller/equal building
22                if (index - j > d || arr[j] >= arr[index]) {
23                    break;
24                }
25                // Update maximum visits if jumping to j gives better result
26                maxVisits = max(maxVisits, 1 + dfs(j));
27            }
28          
29            // Try jumping to the right (indices larger than current)
30            for (int j = index + 1; j < n; ++j) {
31                // Stop if distance exceeds d or we encounter a taller/equal building
32                if (j - index > d || arr[j] >= arr[index]) {
33                    break;
34                }
35                // Update maximum visits if jumping to j gives better result
36                maxVisits = max(maxVisits, 1 + dfs(j));
37            }
38          
39            // Cache and return the result
40            dp[index] = maxVisits;
41            return maxVisits;
42        };
43      
44        // Try starting from each index and find the maximum
45        int result = 1;
46        for (int i = 0; i < n; ++i) {
47            result = max(result, dfs(i));
48        }
49      
50        return result;
51    }
52};
53
1/**
2 * Finds the maximum number of indices that can be visited starting from any index
3 * by jumping between array elements following specific rules
4 * @param arr - Array of building heights
5 * @param d - Maximum jump distance allowed
6 * @returns Maximum number of indices that can be visited
7 */
8function maxJumps(arr: number[], d: number): number {
9    const n = arr.length;
10  
11    // dp[i] stores the maximum number of indices that can be visited starting from index i
12    const dp: number[] = new Array(n).fill(0);
13  
14    /**
15     * DFS with memoization to calculate maximum jumps from a given index
16     * @param index - Starting index for the jump sequence
17     * @returns Maximum number of indices visitable from this index
18     */
19    const dfs = (index: number): number => {
20        // Return cached result if already computed
21        if (dp[index] !== 0) {
22            return dp[index];
23        }
24      
25        // At minimum, we can visit the current index itself
26        let maxVisits = 1;
27      
28        // Try jumping to the left (indices smaller than current)
29        for (let j = index - 1; j >= 0; j--) {
30            // Stop if distance exceeds d or we encounter a taller/equal building
31            if (index - j > d || arr[j] >= arr[index]) {
32                break;
33            }
34            // Update maximum visits if jumping to j gives better result
35            maxVisits = Math.max(maxVisits, 1 + dfs(j));
36        }
37      
38        // Try jumping to the right (indices larger than current)
39        for (let j = index + 1; j < n; j++) {
40            // Stop if distance exceeds d or we encounter a taller/equal building
41            if (j - index > d || arr[j] >= arr[index]) {
42                break;
43            }
44            // Update maximum visits if jumping to j gives better result
45            maxVisits = Math.max(maxVisits, 1 + dfs(j));
46        }
47      
48        // Cache and return the result
49        dp[index] = maxVisits;
50        return maxVisits;
51    };
52  
53    // Try starting from each index and find the maximum
54    let result = 1;
55    for (let i = 0; i < n; i++) {
56        result = Math.max(result, dfs(i));
57    }
58  
59    return result;
60}
61

Time and Space Complexity

Time Complexity: O(n * d)

The analysis breaks down as follows:

  • The dfs function is called for each index in the array, which gives us n unique calls due to memoization with @cache
  • For each dfs(i) call, we explore up to d positions to the left and up to d positions to the right
  • In the worst case, we check O(d) positions in each direction before either hitting the distance limit or finding a taller/equal element
  • Since each state is computed only once due to caching, the total work is O(n) states × O(d) work per state = O(n * d)

Space Complexity: O(n)

The space usage consists of:

  • The recursion stack depth can go up to O(n) in the worst case (when we can jump from index 0 to 1 to 2... to n-1 in a chain)
  • The @cache decorator stores up to n different results, one for each index, using O(n) space
  • Total space complexity is O(n) for the cache plus O(n) for the recursion stack = O(n)

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

Common Pitfalls

1. Incorrect Height Constraint Check Between Indices

One of the most common mistakes is not properly checking that ALL intermediate values between the starting and destination indices are smaller than the starting value. Many developers incorrectly only check the destination value:

Incorrect Implementation:

# WRONG: Only checks destination, ignores intermediate values
for j in range(i - 1, max(i - d - 1, -1), -1):
    if arr[j] < arr[i]:  # Missing check for intermediate values!
        ans = max(ans, 1 + dfs(j))

Why it's wrong: According to the problem, you can only jump from i to j if arr[k] < arr[i] for ALL indices k between i and j. The above code would incorrectly allow jumping over taller intermediate elements.

Correct Approach:

# CORRECT: Breaks immediately when hitting a taller/equal element
for j in range(i - 1, -1, -1):
    if i - j > d or arr[j] >= arr[i]:
        break  # Can't jump past this point
    ans = max(ans, 1 + dfs(j))

2. Off-by-One Errors in Distance Calculation

Another frequent mistake is incorrectly calculating the jump distance or using wrong comparison operators:

Incorrect Examples:

# WRONG: Uses >= instead of >
if i - j >= d or arr[j] >= arr[i]:  # Should be i - j > d
    break

# WRONG: Includes current index in distance calculation
for j in range(i - d, i):  # Should ensure j != i and proper bounds

Correct Implementation: The distance between indices should be strictly compared: i - j > d (not >=), and we must ensure we never jump to the same index.

3. Forgetting to Handle Edge Cases

Common Edge Case Mistakes:

  • Not handling single-element arrays properly
  • Incorrectly setting loop boundaries that could cause index out of bounds
  • Not considering that you can start from ANY index (not just index 0)

Prevention: Always validate loop boundaries:

# Safe boundary checking
for j in range(i - 1, -1, -1):  # Automatically stops at 0
    if i - j > d:  # Distance check prevents going too far
        break

4. Missing or Incorrect Memoization

Without proper memoization, the solution becomes exponentially slow:

Incorrect:

def maxJumps(self, arr, d):
    def dfs(i):  # No memoization - will cause TLE!
        # ... rest of logic
    return max(dfs(i) for i in range(len(arr)))

Correct: Use @cache decorator or manual memoization to ensure each state is computed only once.

5. Breaking Too Early in the Loop

Some might think they can optimize by breaking when finding any valid jump:

Incorrect:

for j in range(i - 1, -1, -1):
    if i - j > d or arr[j] >= arr[i]:
        break
    ans = max(ans, 1 + dfs(j))
    break  # WRONG: Stops after first valid jump, missing better options

Why it's wrong: You need to explore ALL valid jumps from each position to find the maximum path length. Breaking after the first valid jump would miss potentially longer paths.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

You are given an array of intervals where intervals[i] = [start_i, end_i] represent the start and end of the ith interval. You need to merge all overlapping intervals and return an array of the non-overlapping intervals that cover all the intervals in the input.


Recommended Readings

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

Load More