Jump Game V
HardPrompt
Given an array of integers arr and an integer d. In one step you can jump from index i to index:
i + xwhere:i + x < arr.lengthand0 < x <= d.i - xwhere:i - x >= 0and0 < x <= d.
In addition, you can only jump from index i to index j if arr[i] > arr[j] and arr[i] > arr[k] for all indices k between i and j (More formally min(i, j) < k < max(i, j)).
You can choose any index of the array and start jumping. Return the maximum number of indices you can visit.
Notice that you can not jump outside of the array at any time.
Example 1:
Input: arr = [6,4,14,6,8,13,9,7,10,6,12], d = 2
Output: 4
Explanation: You can start at index 10. You can jump 10 --> 8 --> 6 --> 7 as shown.
Note that if you start at index 6 you can only jump to index 7. You cannot jump to index 5 because 13 > 9. You cannot jump to index 4 because index 5 is between index 4 and 6 and 13 > 9.
Similarly You cannot jump from index 3 to index 2 or index 1.Example 2:
Input: arr = [3,3,3,3,3], d = 3
Output: 1
Explanation: You can start at any index. You always cannot jump to any index.Example 3:
Input: arr = [7,6,5,4,3,2,1], d = 1
Output: 7
Explanation: Start at index 0. You can visit all the indicies.
Constraints:
1 <= arr.length <= 10001 <= arr[i] <= 1051 <= d <= arr.length
Approaches
2 approaches with complexity analysis and trade-offs.
This approach significantly improves upon the brute-force method by using memoization, a key technique in dynamic programming. We observe that the recursive function dfs(i) is called multiple times with the same index i, leading to redundant computations. By storing the result of dfs(i) the first time it's computed and reusing it for subsequent calls, we can reduce the time complexity from exponential to polynomial. This is also known as top-down dynamic programming.
Algorithm
- Create a memoization array
memoof sizen, initialized to 0.memo[i]will store the result of the longest path starting fromi. - Define a recursive function
dfs(i). - Inside
dfs(i), first check ifmemo[i]is non-zero. If so, a result has been computed, so returnmemo[i]. - Initialize
res = 1. - Explore jumps to the right (from
i+1tomin(i+d, n-1)). Stop if a blocking element (arr[j] >= arr[i]) is found. For each valid jump toj, updateres = max(res, 1 + dfs(j)). - Explore jumps to the left (from
i-1tomax(0, i-d)) similarly. - Store the final result in
memo[i] = resand return it. - The main function calls
dfs(i)for allifrom0ton-1and returns the maximum value obtained.
Walkthrough
We augment the recursive solution with a memoization table, typically an array memo, to store the results of subproblems. memo[i] will hold the maximum number of indices that can be visited starting from index i.
The algorithm is as follows:
- Initialize a
memoarray of the same size asarrwith a sentinel value (e.g., 0) to indicate that the result for that index has not been computed yet. - Modify the
dfs(i)function. At the beginning of the function, check ifmemo[i]already contains a computed result. If it does, return that value immediately. - If
memo[i]has not been computed, proceed with the same logic as the brute-force approach: calculate the maximum path length by exploring valid jumps to the left and right and making recursive calls. - Once the result for
iis computed, store it inmemo[i]before returning. - The main loop still iterates through all indices
ias starting points, calling the memoizeddfs(i)and tracking the overall maximum.
This ensures that the longest path for each index is calculated exactly once.
class Solution { int[] memo; int[] arr; int d; int n; public int maxJumps(int[] arr, int d) { this.n = arr.length; this.arr = arr; this.d = d; this.memo = new int[n]; int maxJumps = 0; for (int i = 0; i < n; i++) { maxJumps = Math.max(maxJumps, dfs(i)); } return maxJumps; } private int dfs(int i) { if (memo[i] != 0) { return memo[i]; } int res = 1; // Jump to the right for (int j = i + 1; j <= Math.min(i + d, n - 1); j++) { if (arr[j] >= arr[i]) { break; } res = Math.max(res, 1 + dfs(j)); } // Jump to the left for (int j = i - 1; j >= Math.max(0, i - d); j--) { if (arr[j] >= arr[i]) { break; } res = Math.max(res, 1 + dfs(j)); } memo[i] = res; return res; }}Complexity
Time
O(n * d). Each state `dfs(i)` for `i` from 0 to `n-1` is computed exactly once. The work done within each call is proportional to `d` because of the two loops exploring up to `d` indices in each direction.
Space
O(n), where n is the array length. This space is used for the memoization array `memo` and the recursion stack.
Trade-offs
Pros
Highly efficient and sufficient to pass the given constraints.
Often intuitive to derive from a brute-force recursive solution.
Asymptotically faster than a sorting-based bottom-up approach for this problem.
Cons
Relies on recursion, which can have overhead and stack depth limitations in some environments (though not an issue for n=1000).
Slightly more complex than the brute-force approach due to the memoization table.
This approach directly translates the problem statement into a recursive solution. We define a function that explores all possible valid jump sequences starting from a given index. For each index in the array, we initiate this exploration and find the length of the longest path. The final answer is the maximum length found across all possible starting indices.
Algorithm
- Define a recursive function
dfs(i)that returns the maximum number of indices that can be visited starting from indexi. - Inside
dfs(i), initialize a resultmaxVisits = 1(for the current index). - Iterate to the right from
i+1tomin(i+d, n-1). For each indexj, check if the jump is valid. A jump is valid ifarr[j] < arr[i]and all intermediate elements are also smaller thanarr[i]. If an elementarr[k]withk > iis found such thatarr[k] >= arr[i], we cannot jump tokor any index beyond it, so we stop searching in this direction. - If a jump to
jis valid, recursively calldfs(j)and updatemaxVisits = max(maxVisits, 1 + dfs(j)). - Similarly, iterate to the left from
i-1down tomax(0, i-d)and perform the same logic. - Return
maxVisits. - The main function iterates through all possible starting indices
ifrom0ton-1, callsdfs(i), and keeps track of the maximum result found.
Walkthrough
The core of this method is a recursive function, let's call it dfs(i), which calculates the maximum number of indices you can visit starting from index i. This function works as follows:
- The base case for the recursion is a path of length 1, which is just the starting index
iitself. - From index
i, we explore all possible jumps to the right within the distanced. A jump to indexjis valid only ifarr[i]is strictly greater thanarr[j]and all elements betweeniandj. If we encounter an elementarr[k](withkbetweeniandj) that is greater than or equal toarr[i], we cannot jump tojor any index beyondkin that direction. - For each valid jump to an index
j, we recursively calldfs(j)to find the length of the longest path fromj. We then update our current maximum path length fromiwith1 + dfs(j). - We repeat the same process for all possible jumps to the left.
- The main part of the program iterates through every index
ifrom0ton-1, using each as a potential starting point, and returns the overall maximum path length found.
This method explores the entire search space, but it's highly inefficient because it recalculates the longest path for the same index multiple times.
class Solution { public int maxJumps(int[] arr, int d) { int n = arr.length; int maxJumps = 0; for (int i = 0; i < n; i++) { maxJumps = Math.max(maxJumps, dfs(i, arr, d)); } return maxJumps; } private int dfs(int i, int[] arr, int d) { int n = arr.length; int max = 1; // Jump to the right for (int j = i + 1; j <= Math.min(i + d, n - 1); j++) { if (arr[j] >= arr[i]) { break; // Cannot jump over a taller or equal bar } // Valid jump to j max = Math.max(max, 1 + dfs(j, arr, d)); } // Jump to the left for (int j = i - 1; j >= Math.max(i - d, 0); j--) { if (arr[j] >= arr[i]) { break; // Cannot jump over a taller or equal bar } // Valid jump to j max = Math.max(max, 1 + dfs(j, arr, d)); } return max; }}Complexity
Time
O(d^n) in the worst case. The recursion tree can be very deep and wide, with each node having up to 2d branches. This leads to an exponential number of calls, many of which are for the same subproblems.
Space
O(n), where n is the number of elements in the array. This is for the recursion stack depth in the worst case (e.g., a strictly decreasing array).
Trade-offs
Pros
Simple to conceptualize and implement.
Follows the problem description directly.
Cons
Extremely inefficient due to massive re-computation of the same subproblems.
Will result in a 'Time Limit Exceeded' error on most platforms for the given constraints.
Solutions
Solution
class Solution {private int n;private int d;private int[] arr;private Integer[] f;public int maxJumps(int[] arr, int d) { n = arr.length; this.d = d; this.arr = arr; f = new Integer[n]; int ans = 1; for (int i = 0; i < n; ++i) { ans = Math.max(ans, dfs(i)); } return ans; }private int dfs(int i) { if (f[i] != null) { return f[i]; } int ans = 1; for (int j = i - 1; j >= 0; --j) { if (i - j > d || arr[j] >= arr[i]) { break; } ans = Math.max(ans, 1 + dfs(j)); } for (int j = i + 1; j < n; ++j) { if (j - i > d || arr[j] >= arr[i]) { break; } ans = Math.max(ans, 1 + dfs(j)); } return f[i] = ans; }}class Solution {public: int maxJumps(vector<int> &arr, int d) { int n = arr.size(); int f[n]; memset(f, 0, sizeof(f)); function<int(int)> dfs = [&](int i) -> int { if (f[i]) { return f[i]; } int ans = 1; for (int j = i - 1; j >= 0; --j) { if (i - j > d || arr[j] >= arr[i]) { break; } ans = max(ans, 1 + dfs(j)); } for (int j = i + 1; j < n; ++j) { if (j - i > d || arr[j] >= arr[i]) { break; } ans = max(ans, 1 + dfs(j)); } return f[i] = ans; }; int ans = 1; for (int i = 0; i < n; ++i) { ans = max(ans, dfs(i)); } return ans; }};class Solution: def maxJumps(self, arr: List[int], d: int) -> int: @ cache def dfs(i): ans = 1 for j in range(i - 1, - 1, - 1): if i - j > d or arr[j] >= arr[i]: break ans = max(ans, 1 + dfs(j)) for j in range(i + 1, n): if j - i > d or arr[j] >= arr[i]: break ans = max(ans, 1 + dfs(j)) return ans n = len(arr) return max(dfs(i) for i in range(n))Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.