Problem overview
The function jump receives a vector of non-negative integers, nums. You start at index 0, and nums[i] is the farthest you can jump forward from index i: any smaller jump is allowed too. The function returns the minimum number of jumps needed to land on the last index.
For nums = [2, 3, 1, 1, 4] the answer is 2. Jump one step from index 0 to index 1, whose value is 3, then jump three steps to index 4. Jumping two steps to index 2 first would be slower, because index 2 only holds a 1. The tests assume the last index can always be reached.
Approach: reach, and the reach of one more jump
A brute-force search tries every possible landing spot at every jump, and the number of paths grows quickly. A dynamic-programming version stores the best jump count for each index and updates up to nums[i] later indices from every index, which is O(n²) in the worst case. A greedy scan does the work in a single pass.
The idea is to think in layers. After k jumps there is a farthest index you can have reached, and every index up to it is reachable in at most k jumps. Call that boundary cur_max. While scanning, you also track next_max, the farthest index that any index seen so far could reach with one more jump.
When the scan arrives at an index i that is beyond cur_max, the current jumps can't get there. One more jump is unavoidable, and the best place to have jumped from is whichever earlier index reaches farthest, which is exactly what next_max remembers. So the jump count goes up by one and cur_max becomes next_max.
Take nums = [2, 3, 1, 1, 4]:
- Start:
result = 1,cur_max = 2,next_max = 2. The first jump is counted from the start and covers indices 1 and 2. i = 1: inside the reach.next_maxbecomesmax(2, 1 + 3) = 4.i = 2: inside the reach.next_maxstays 4.i = 3:cur_max = 2 < 3, so a second jump is needed.result = 2andcur_max = 4.i = 4: inside the new reach, so the scan ends withresult = 2.
Step by step
- Handle short vectors first. With zero or one element there is nothing to jump over, so the answer is 0. This check also keeps
nums[0]in the next steps from panicking on an empty vector. - Start
resultat1i32. With two or more elements at least one jump is needed, and the first one is counted right away. Thei32suffix matches the return type. - Set
cur_maxtonums[0] as usize. The first jump reaches at most this index. It is ausizebecause it will be compared with loop indices. - Set
next_maxto the same value. Before the scan has seen anything else, index 0 is the only starting point for the next jump. - Loop over
1..nums.len(). Index 0 is already accounted for in the initial values, so the scan visits every later index once. - Check whether the index is out of reach. If
cur_max < i, add a jump and move the boundary tonext_max. - Update
next_maxfor every index, including the one that just triggered a jump, since that index may be the launch point of the following jump. - Return
resultas the final expression, without a semicolon.
The solution in Rust
impl Solution {
fn jump(nums: Vec<i32>) -> i32 {
if nums.len() <= 1 {
return 0;
}
let mut result = 1i32;
let mut cur_max = nums[0] as usize;
let mut next_max = nums[0] as usize;
for i in 1..nums.len() {
if cur_max < i {
result += 1;
cur_max = next_max;
}
next_max = next_max.max(i + nums[i] as usize);
}
result
}
}
The Rust details worth a closer look:
- Casts and precedence. In
i + nums[i] as usize, theascast binds tighter than+, so onlynums[i]is converted and the sum is ausize. Rust never mixes integer types in one expression. A negativei32would wrap to a hugeusizein anascast, which is why the non-negative guarantee matters. maxis a method.next_max.max(x)returns the larger of two values of the same ordered type. There is no need for anifor a free function.- Mutable bindings.
result,cur_maxandnext_maxchange during the scan, so they are declared withlet mut. Withoutmut, the compiler rejects the assignments. - The range
1..nums.len()excludes its upper bound, so the last valid index is visited and nothing out of bounds is. Indexing a vector outside its length panics.
Complexity
Time: O(n). The loop touches each index once and does a constant amount of work per index.
Space: O(1). The function keeps three integers, however long nums is.
Edge cases and common mistakes
- One element.
[5]returns 0: you already stand on the last index. The early return handles it before the code reads anything. - Two elements.
[5, 1]returns 1. The initialresult = 1is the first jump, and the loop never needs to add another. - Counting the first jump twice. Starting
resultat 0 and also counting a jump at the start of the loop gives answers one too large. - Updating
cur_maxon every index. The boundary may only move when the scan runs past it. Moving it earlier would count jumps that aren't needed. - Skipping the
next_maxupdate on the jump index. The index where the scan runs out of reach can itself be the best launch point. - An unreachable end. The tests assume the last index is reachable. For
[0, 1]this code returns 2 instead of reporting a failure, so it isn't a reachability check.
Testing
[2, 3, 1, 1, 4]gives 2.[1, 1, 1, 1]gives 3.[5, 1, 1]gives 1.[3, 1, 1, 1, 1]gives 2.[1, 3, 1, 1, 1, 1]gives 3.[7]gives 0.
