3660. Jump Game IX
Problem Description
You are given an integer array nums
. From any index i
, you can jump to other indices following specific rules:
- You can jump forward from index
i
to indexj
(wherej > i
) only ifnums[j] < nums[i]
- You can jump backward from index
i
to indexj
(wherej < i
) only ifnums[j] > nums[i]
In other words, when jumping forward, you must land on a smaller value, and when jumping backward, you must land on a larger value.
For each starting index i
, you need to find the maximum value in nums
that can be reached by following any valid sequence of jumps. You can make multiple jumps in sequence as long as each individual jump follows the rules above.
Return an array ans
where ans[i]
represents the maximum value that can be reached starting from index i
.
For example, if you start at index i
, you might jump forward to a smaller value at index j
, then from j
jump backward to a larger value at index k
, and continue jumping following the rules. Among all values you can reach through any valid jumping sequence, ans[i]
should be the maximum one.
Intuition
Let's think about the jumping pattern. From any index i
, we can jump forward to smaller values and backward to larger values. This creates an alternating pattern: if we jump forward (to a smaller value), the next jump from there must be backward (to a larger value), and vice versa.
A key observation is that if we're at position i
and want to maximize the value we can reach, we should consider what happens when we make our first jump. If we jump forward to some position j
where nums[j] < nums[i]
, then from j
we can only jump backward to positions with larger values than nums[j]
.
Here's the critical insight: if there exists a position to the left of i
with the maximum possible value (let's call it preMax[i]
), and a position to the right of i
with the minimum possible value (let's call it sufMin
), we can potentially create a jumping sequence that reaches both extremes.
Consider this scenario: if preMax[i] > sufMin
, it means the maximum value to the left is greater than some minimum value to the right. We can jump from i
to the position of sufMin
(forward jump to smaller value), then from there jump back to the position of preMax[i]
(backward jump to larger value). This allows us to reach the maximum value in the left portion.
However, if preMax[i] ≤ sufMin
, we cannot make this beneficial jumping pattern, so the best we can reach is just preMax[i]
itself.
The clever part is processing from right to left. When we're at position i
, we already know ans[i+1]
(the maximum reachable from i+1
). If we can establish the jumping pattern described above (preMax[i] > sufMin
), then we can first reach the same positions that i+1
can reach, making ans[i] = ans[i+1]
. Otherwise, we're limited to preMax[i]
.
This dynamic programming approach efficiently computes the answer by maintaining the prefix maximum array and suffix minimum value, avoiding redundant calculations of reachable positions.
Solution Approach
The solution uses dynamic programming with prefix maximum and suffix minimum tracking. Here's the step-by-step implementation:
1. Initialize Data Structures:
- Create an answer array
ans
of sizen
initialized with zeros - Create a prefix maximum array
pre_max
wherepre_max[i]
stores the maximum value from index 0 to i - Initialize a suffix minimum variable
suf_min
to infinity
2. Build Prefix Maximum Array:
pre_max = [nums[0]] * n
for i in range(1, n):
pre_max[i] = max(pre_max[i - 1], nums[i])
This ensures pre_max[i]
contains the maximum value in the range [0, i]
.
3. Process from Right to Left: Starting from the last index and moving backwards:
suf_min = inf
for i in range(n - 1, -1, -1):
ans[i] = ans[i + 1] if pre_max[i] > suf_min else pre_max[i]
suf_min = min(suf_min, nums[i])
4. Decision Logic at Each Position:
-
If
pre_max[i] > suf_min
: This means we can create a beneficial jumping pattern:- Jump forward from
i
to wheresuf_min
is located (valid sincesuf_min < nums[i]
) - From there, jump backward to where
pre_max[i]
is located (valid sincepre_max[i] > suf_min
) - Continue with the same jumping possibilities as position
i + 1
- Therefore,
ans[i] = ans[i + 1]
- Jump forward from
-
If
pre_max[i] ≤ suf_min
: We cannot create the beneficial pattern, so the best we can reach ispre_max[i]
itself
5. Update Suffix Minimum:
After processing position i
, update suf_min = min(suf_min, nums[i])
to maintain the minimum value seen so far from the right.
Time Complexity: O(n)
- Two passes through the array
Space Complexity: O(n)
- For the pre_max
and ans
arrays
The algorithm cleverly avoids explicitly tracking all possible jump sequences by recognizing that the maximum reachable value depends on whether we can establish a specific jumping pattern between the prefix maximum and suffix minimum.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with nums = [5, 2, 7, 1, 4]
.
Step 1: Build prefix maximum array
pre_max[0] = 5
(max from index 0 to 0)pre_max[1] = max(5, 2) = 5
(max from index 0 to 1)pre_max[2] = max(5, 7) = 7
(max from index 0 to 2)pre_max[3] = max(7, 1) = 7
(max from index 0 to 3)pre_max[4] = max(7, 4) = 7
(max from index 0 to 4)
So pre_max = [5, 5, 7, 7, 7]
Step 2: Process from right to left
Index 4 (nums[4] = 4):
suf_min = ∞
pre_max[4] = 7 > suf_min = ∞
? Noans[4] = pre_max[4] = 7
- Update:
suf_min = min(∞, 4) = 4
Index 3 (nums[3] = 1):
suf_min = 4
pre_max[3] = 7 > suf_min = 4
? Yesans[3] = ans[4] = 7
- Update:
suf_min = min(4, 1) = 1
Index 2 (nums[2] = 7):
suf_min = 1
pre_max[2] = 7 > suf_min = 1
? Yesans[2] = ans[3] = 7
- Update:
suf_min = min(1, 7) = 1
Index 1 (nums[1] = 2):
suf_min = 1
pre_max[1] = 5 > suf_min = 1
? Yesans[1] = ans[2] = 7
- Update:
suf_min = min(1, 2) = 1
Index 0 (nums[0] = 5):
suf_min = 1
pre_max[0] = 5 > suf_min = 1
? Yesans[0] = ans[1] = 7
- Update:
suf_min = min(1, 5) = 1
Final Result: ans = [7, 7, 7, 7, 7]
Verification for index 0: Starting at index 0 (value 5), we can:
- Jump forward to index 3 (value 1, which is < 5) ✓
- From index 3, jump backward to index 2 (value 7, which is > 1) ✓
- We've reached value 7, the maximum possible!
The algorithm correctly identifies that when pre_max[i] > suf_min
, we can create a jumping pattern that reaches the maximum value in the array.
Solution Implementation
1class Solution:
2 def maxValue(self, nums: List[int]) -> List[int]:
3 n = len(nums)
4
5 # Initialize result array with zeros
6 ans = [0] * n
7
8 # Build prefix maximum array
9 # pre_max[i] stores the maximum value from index 0 to i
10 pre_max = [nums[0]] * n
11 for i in range(1, n):
12 pre_max[i] = max(pre_max[i - 1], nums[i])
13
14 # Initialize suffix minimum to infinity
15 suf_min = float('inf')
16
17 # Traverse from right to left to build answer array
18 for i in range(n - 1, -1, -1):
19 # If prefix max up to i is greater than suffix min after i,
20 # propagate the value from the right (if valid index)
21 # Otherwise, use the prefix max at current position
22 if i + 1 < n:
23 ans[i] = ans[i + 1] if pre_max[i] > suf_min else pre_max[i]
24 else:
25 # For the last element, use prefix max if condition fails
26 ans[i] = pre_max[i] if pre_max[i] <= suf_min else 0
27
28 # Update suffix minimum including current element
29 suf_min = min(suf_min, nums[i])
30
31 return ans
32
1class Solution {
2 public int[] maxValue(int[] nums) {
3 int n = nums.length;
4 int[] result = new int[n];
5
6 // Build prefix maximum array
7 // prefixMax[i] stores the maximum value from nums[0] to nums[i]
8 int[] prefixMax = new int[n];
9 prefixMax[0] = nums[0];
10 for (int i = 1; i < n; i++) {
11 prefixMax[i] = Math.max(prefixMax[i - 1], nums[i]);
12 }
13
14 // Initialize suffix minimum with a large value (approximately Integer.MAX_VALUE)
15 int suffixMin = 1 << 30;
16
17 // Traverse from right to left to compute result
18 // For each position, check if prefix maximum is greater than suffix minimum
19 for (int i = n - 1; i >= 0; i--) {
20 // If prefix max up to current position is greater than suffix min after current position,
21 // use the next result value (or 0 for last element)
22 // Otherwise, use the prefix max value
23 if (prefixMax[i] > suffixMin) {
24 result[i] = (i + 1 < n) ? result[i + 1] : 0;
25 } else {
26 result[i] = prefixMax[i];
27 }
28
29 // Update suffix minimum to include current element
30 suffixMin = Math.min(suffixMin, nums[i]);
31 }
32
33 return result;
34 }
35}
36
1class Solution {
2public:
3 vector<int> maxValue(vector<int>& nums) {
4 int n = nums.size();
5
6 // Result array to store the final values
7 vector<int> result(n);
8
9 // Build prefix maximum array: prefixMax[i] = max(nums[0], nums[1], ..., nums[i])
10 vector<int> prefixMax(n);
11 prefixMax[0] = nums[0];
12 for (int i = 1; i < n; ++i) {
13 prefixMax[i] = max(prefixMax[i - 1], nums[i]);
14 }
15
16 // Initialize suffix minimum with a large value (approximately INT_MAX)
17 int suffixMin = 1 << 30;
18
19 // Traverse from right to left, building result array while tracking suffix minimum
20 for (int i = n - 1; i >= 0; --i) {
21 // If prefix max up to position i is greater than suffix min after position i,
22 // copy the value from the next position (if valid), otherwise use prefix max
23 if (prefixMax[i] > suffixMin) {
24 // Copy from next position if within bounds
25 result[i] = (i + 1 < n) ? result[i + 1] : prefixMax[i];
26 } else {
27 result[i] = prefixMax[i];
28 }
29
30 // Update suffix minimum: min(nums[i], nums[i+1], ..., nums[n-1])
31 suffixMin = min(suffixMin, nums[i]);
32 }
33
34 return result;
35 }
36};
37
1/**
2 * Computes the maximum value array based on prefix maximum and suffix minimum
3 * @param nums - Input array of numbers
4 * @returns Array where each element represents a computed maximum value
5 */
6function maxValue(nums: number[]): number[] {
7 const n: number = nums.length;
8
9 // Initialize result array with zeros
10 const result: number[] = Array(n).fill(0);
11
12 // Build prefix maximum array where prefixMax[i] stores max value from index 0 to i
13 const prefixMax: number[] = Array(n).fill(nums[0]);
14 for (let i = 1; i < n; i++) {
15 prefixMax[i] = Math.max(prefixMax[i - 1], nums[i]);
16 }
17
18 // Initialize suffix minimum with a large value (2^30)
19 let suffixMin: number = 1 << 30;
20
21 // Traverse from right to left to compute result and update suffix minimum
22 for (let i = n - 1; i >= 0; i--) {
23 // If prefix max at current position is greater than suffix min,
24 // use the next element's result; otherwise use current prefix max
25 result[i] = prefixMax[i] > suffixMin ? result[i + 1] : prefixMax[i];
26
27 // Update suffix minimum with current element
28 suffixMin = Math.min(suffixMin, nums[i]);
29 }
30
31 return result;
32}
33
Time and Space Complexity
Time Complexity: O(n)
The algorithm consists of three main parts:
- First loop (lines 5-6): Iterates through the array once to build the
pre_max
array, performingO(1)
operations per iteration →O(n)
- Second loop (lines 8-10): Iterates through the array once in reverse, performing
O(1)
operations per iteration →O(n)
- Total:
O(n) + O(n) = O(n)
Space Complexity: O(n)
The algorithm uses the following additional space:
ans
array of sizen
→O(n)
pre_max
array of sizen
→O(n)
suf_min
variable →O(1)
- Total:
O(n) + O(n) + O(1) = O(n)
Where n
is the length of the array nums
.
Common Pitfalls
1. Incorrect Handling of the Last Element
The provided code has a bug when processing the last element (index n-1
). When i = n-1
, there's no element to the right, so we cannot jump forward. The code incorrectly sets ans[n-1] = 0
when pre_max[i] > suf_min
, but it should be pre_max[n-1]
(which equals nums[n-1]
for the last element).
Problematic Code:
if i + 1 < n: ans[i] = ans[i + 1] if pre_max[i] > suf_min else pre_max[i] else: ans[i] = pre_max[i] if pre_max[i] <= suf_min else 0 # Bug: returns 0
Corrected Code:
if i + 1 < n: ans[i] = ans[i + 1] if pre_max[i] > suf_min else pre_max[i] else: ans[i] = pre_max[i] # Last element can only reach itself or earlier max
2. Misunderstanding the Jump Logic
A common misconception is thinking that pre_max[i] > suf_min
directly enables a jump. Actually, this condition checks if there exists a valid jumping sequence:
- From position
i
, we can potentially jump forward to some positionj > i
wherenums[j] < nums[i]
- From that position
j
, we might jump backward to reachpre_max[i]
- The condition ensures such a sequence exists somewhere in the suffix
Correct Understanding:
suf_min
represents the minimum value to the right ofi
- If
pre_max[i] > suf_min
, we know there's at least one position to the right with a value smaller than the maximum we've seen so far - This enables a forward-backward jump pattern to reach higher values
3. Off-by-One Error in Suffix Minimum Update
The suffix minimum should be updated AFTER processing the current position, not before. The current implementation is correct, but moving the update line could break the logic:
Wrong Order:
for i in range(n - 1, -1, -1):
suf_min = min(suf_min, nums[i]) # Wrong: updates before using
ans[i] = ans[i + 1] if pre_max[i] > suf_min else pre_max[i]
Correct Order:
for i in range(n - 1, -1, -1):
ans[i] = ans[i + 1] if pre_max[i] > suf_min else pre_max[i]
suf_min = min(suf_min, nums[i]) # Correct: updates after using
Complete Corrected Solution:
class Solution:
def maxValue(self, nums: List[int]) -> List[int]:
n = len(nums)
ans = [0] * n
# Build prefix maximum array
pre_max = [nums[0]] * n
for i in range(1, n):
pre_max[i] = max(pre_max[i - 1], nums[i])
suf_min = float('inf')
# Process from right to left
for i in range(n - 1, -1, -1):
if i + 1 < n:
# Can potentially jump forward and utilize future possibilities
ans[i] = ans[i + 1] if pre_max[i] > suf_min else pre_max[i]
else:
# Last element: maximum reachable is pre_max[i]
ans[i] = pre_max[i]
# Update suffix minimum after processing current position
suf_min = min(suf_min, nums[i])
return ans
How many ways can you arrange the three letters A, B and C?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!