A clear explanation of counting good starting indices using next-jump preprocessing and dynamic programming.
Problem Restatement
We are given an integer array arr.
From any starting index, we make jumps to the right. The jumps are numbered by jump count:
| Jump number | Type |
|---|---|
1st, 3rd, 5th, … | Odd jump |
2nd, 4th, 6th, … | Even jump |
For an odd jump from index i, we must jump to an index j such that:
i < j
arr[i] <= arr[j]Among all possible j, choose the one with the smallest possible arr[j]. If there is a tie, choose the smallest index j.
For an even jump from index i, we must jump to an index j such that:
i < j
arr[i] >= arr[j]Among all possible j, choose the one with the largest possible arr[j]. If there is a tie, choose the smallest index j.
A starting index is good if we can reach the last index by following these rules.
Return the number of good starting indices.
The official constraints are 1 <= arr.length <= 2 * 10^4 and 0 <= arr[i] < 10^5.
Input and Output
| Item | Meaning |
|---|---|
| Input | Integer array arr |
| Output | Number of good starting indices |
| Odd jump | Jump to the smallest value greater than or equal to current value |
| Even jump | Jump to the largest value less than or equal to current value |
| Tie rule | If values tie, use the smallest index |
Example function shape:
def oddEvenJumps(arr: list[int]) -> int:
...Examples
Example 1:
arr = [10, 13, 12, 14, 15]Output:
2The good starting indices are 3 and 4.
From index 3, the first odd jump goes to index 4.
Index 4 is already the last index, so it is good.
Example 2:
arr = [2, 3, 1, 1, 4]Output:
3The good starting indices are 1, 3, and 4.
Example 3:
arr = [5, 1, 3, 4, 2]Output:
3The good starting indices are 1, 2, and 4.
First Thought: Simulate From Every Index
A direct idea is to start from every index and simulate jumps.
For each jump, we search all later indices to find the required destination.
For odd jumps, search for the smallest value greater than or equal to arr[i].
For even jumps, search for the largest value less than or equal to arr[i].
This can be too slow.
There are n possible starts, and each jump search may scan O(n) elements. We need to precompute jump destinations efficiently.
Key Insight
Separate the problem into two parts.
First, compute:
next_odd[i]the index reached by an odd jump from i.
Also compute:
next_even[i]the index reached by an even jump from i.
Then use dynamic programming:
odd[i] = can reach the end from i when the next jump is odd
even[i] = can reach the end from i when the next jump is evenThe last index is always good:
odd[n - 1] = True
even[n - 1] = TrueThen:
odd[i] = even[next_odd[i]]
even[i] = odd[next_even[i]]if those next jumps exist.
Computing Next Odd Jumps
For odd jumps, we need the smallest value to the right that is greater than or equal to arr[i].
If multiple indices have that same value, choose the smallest index.
We can compute this with sorting and a monotonic stack.
Sort indices by:
(arr[i], i)This puts smaller values first. For equal values, smaller indices come first.
Then use a stack to assign next greater-or-equal destinations.
When we scan sorted indices, if the current index is greater than the stack top, then the current index is the correct next odd jump for that stack-top index.
Computing Next Even Jumps
For even jumps, we need the largest value to the right that is less than or equal to arr[i].
Sort indices by:
(-arr[i], i)This puts larger values first. For equal values, smaller indices come first.
Then use the same monotonic stack process.
This assigns next lower-or-equal destinations.
Helper Idea
The same helper can compute next jumps from an ordering of indices.
def make_next(indices):
stack = []
result = [-1] * n
for index in indices:
while stack and index > stack[-1]:
result[stack.pop()] = index
stack.append(index)
return resultWhy this works:
The sorted order guarantees value priority.
The stack logic guarantees the destination is to the right and has the smallest possible index among valid destinations.
Algorithm
- Let
n = len(arr). - Build
next_odd:- Sort indices by
(arr[i], i). - Run the monotonic stack helper.
- Sort indices by
- Build
next_even:- Sort indices by
(-arr[i], i). - Run the same helper.
- Sort indices by
- Create arrays:
odd = [False] * n
even = [False] * n- Set:
odd[n - 1] = True
even[n - 1] = True- Process indices from right to left:
- If
next_odd[i] != -1, setodd[i] = even[next_odd[i]]. - If
next_even[i] != -1, seteven[i] = odd[next_even[i]].
- If
- Return the number of
Truevalues inodd.
We count odd because the first jump from a starting index is jump number 1, which is odd.
Correctness
The sorted-index helper computes the correct odd and even jump destinations.
For odd jumps, sorting by (arr[i], i) considers candidate destinations in increasing value order, and for equal values in increasing index order. Therefore, the first valid right-side candidate assigned to an index is exactly the required smallest value greater than or equal to arr[i], with the smallest index tie-break.
For even jumps, sorting by (-arr[i], i) considers candidate destinations in decreasing value order, and for equal values in increasing index order. Therefore, the first valid right-side candidate assigned to an index is exactly the required largest value less than or equal to arr[i], with the smallest index tie-break.
After jump destinations are known, the dynamic programming recurrence is direct. If the next jump from index i is odd, we must go to next_odd[i], and after that the next jump is even. So odd[i] is true exactly when even[next_odd[i]] is true. The even case is symmetric.
The last index is reachable from itself with no further jumps, so both odd[n - 1] and even[n - 1] are true.
Since the DP processes indices from right to left and every jump goes to a larger index, all needed future states are already computed. Therefore, odd[i] correctly tells whether starting at i can reach the end. Counting true values in odd gives exactly the number of good starting indices.
Complexity
Let n = len(arr).
| Metric | Value | Why |
|---|---|---|
| Time | O(n log n) | Sorting indices dominates |
| Space | O(n) | Next-jump arrays, DP arrays, and stack |
Implementation
class Solution:
def oddEvenJumps(self, arr: list[int]) -> int:
n = len(arr)
def make_next(indices: list[int]) -> list[int]:
result = [-1] * n
stack = []
for index in indices:
while stack and index > stack[-1]:
result[stack.pop()] = index
stack.append(index)
return result
odd_order = sorted(range(n), key=lambda i: (arr[i], i))
even_order = sorted(range(n), key=lambda i: (-arr[i], i))
next_odd = make_next(odd_order)
next_even = make_next(even_order)
odd = [False] * n
even = [False] * n
odd[-1] = True
even[-1] = True
for i in range(n - 2, -1, -1):
if next_odd[i] != -1:
odd[i] = even[next_odd[i]]
if next_even[i] != -1:
even[i] = odd[next_even[i]]
return sum(odd)Code Explanation
The helper builds next-jump destinations:
def make_next(indices: list[int]) -> list[int]:It receives indices in the correct value order.
The stack stores indices whose jump target has not been found yet:
stack = []When the current index is to the right of the stack top:
while stack and index > stack[-1]:
result[stack.pop()] = indexthe current index becomes that previous index’s jump destination.
For odd jumps:
odd_order = sorted(range(n), key=lambda i: (arr[i], i))This handles smallest greater-or-equal value and smallest index tie-break.
For even jumps:
even_order = sorted(range(n), key=lambda i: (-arr[i], i))This handles largest smaller-or-equal value and smallest index tie-break.
The DP arrays mean:
odd[i]
even[i]whether we can reach the end from index i when the next jump is odd or even.
The last index is always reachable:
odd[-1] = True
even[-1] = TrueThen we fill states from right to left:
for i in range(n - 2, -1, -1):If an odd jump exists:
odd[i] = even[next_odd[i]]If an even jump exists:
even[i] = odd[next_even[i]]Finally:
return sum(odd)counts good starting indices.
Testing
def run_tests():
s = Solution()
assert s.oddEvenJumps([10, 13, 12, 14, 15]) == 2
assert s.oddEvenJumps([2, 3, 1, 1, 4]) == 3
assert s.oddEvenJumps([5, 1, 3, 4, 2]) == 3
assert s.oddEvenJumps([1]) == 1
assert s.oddEvenJumps([1, 2, 3, 4]) == 2
assert s.oddEvenJumps([4, 3, 2, 1]) == 1
assert s.oddEvenJumps([2, 2, 2]) == 3
assert s.oddEvenJumps([1, 3, 2, 4, 5]) == 4
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
[10,13,12,14,15] | Main example |
[2,3,1,1,4] | Duplicate values and tie handling |
[5,1,3,4,2] | Mixed odd and even jumps |
[1] | Last index is good by default |
| Increasing array | Only some starts can alternate successfully |
| Decreasing array | Only last index is good |
| All equal values | Every index can jump to the next index |
| Mixed increasing values | Checks DP propagation |