Jump Game II
A medium problem from Greedy. Try it first, open a hint when you are stuck, and read the solution when you are done.
- Medium
- Greedy
The problem
You start on position 0 of nums. Each number is the longest jump forward you may make from its position, and any shorter jump is allowed too. Return the smallest number of jumps that takes you to the last position. The last position can always be reached. If the list has one number you are already there, so the answer is 0.
What to write
function solve(nums: number[]): numberExample 1
Input nums = [2,3,1,1,4] Output 2
One jump is not enough: from position 0 it gets you to position 2 at most, and the last position is 4. Two jumps are enough: go 1 step to position 1, whose number is 3, then 3 steps to position 4.
Example 2
Input nums = [2,3,0,1,4] Output 2
One jump from position 0 ends on position 1 or 2, short of the last position, 4. Nothing leaves position 2, because its number is 0. Position 1 holds a 3, and 3 steps from there land exactly on position 4. That makes two jumps.
Constraints
The last index is guaranteed reachable.
Hints
Each hint gives away a little more. Open one only when you need it.
Hint 1: Use the constraints
The last position can always be reached, so the question is never whether you arrive, only how few jumps you need. Any shorter jump is allowed, and jumps only go forward. A list with one number needs 0 jumps.
Hint 2: Ask a smaller question
In 2, 3, 1, 1, 4, which positions can you stand on after one jump? Which further positions does a second jump open up? Write both groups down. What do you notice about the shape of each group, and which group holds the last position?
Hint 3: Name the pattern
The positions that need one jump form an unbroken stretch, and so do those that need two, and so on. So count stretches. The next stretch ends at the farthest spot that any position of the current stretch can jump to. Always claiming the farthest reach is a greedy choice: reaching farther never loses you an option. Read the lesson on greedy choice.
Hint 4: State the invariant
At the top of each round of the loop, the jumps counted are the fewest that reach the position the loop is on, and you know the last position those jumps reach. The farthest spot noted is the best landing spot of one more jump from any position visited so far.
Hint 5: Sketch the pseudocode
end = 0, far = 0, jumps = 0. For each position i with number x, except the last position: far = the larger of far and i + x. If i equals end: add 1 to jumps and set end = far. After the loop, return jumps.
Solution
From here on the page gives the method away. If you are still solving, go back to the hints.
Approach
Scan all positions reachable with the current jump count. When that layer ends, advance to its farthest reachable boundary.
The slow way
Try every path: from each position try every jump length the number allows, count the jumps of each path that arrives, and keep the smallest count. It is correct, but when every number is large enough to reach the end, one more number doubles the work: 8 visits for 4 numbers, over a million for 21.
The insight
The positions you can reach with at most a given number of jumps form an unbroken stretch that starts at position 0, because shorter jumps are allowed. So there is no need to follow paths. Ask where the stretch for 0 jumps ends, then for 1 jump, then for 2, and stop when a stretch takes in the last position. That count is the fewest jumps.
Step by step
- Start with 0 jumps, which reach position 0 and no farther. Three numbers are kept: the jumps counted, the last position those jumps can reach, and the farthest spot one more jump could land on. Before any jump, the count is 0 and both positions are 0.
- Visit every position except the last one. The last position is where you want to arrive, not a place to jump from. Visiting it would count a jump too many whenever the reach ends exactly there.
- Note the farthest spot that one more jump could land on. The next jump may start from any position the counted jumps reach. The longest jump from this position lands on the position plus its number, and the largest of those spots is where the next jump's reach will end.
- This is the last position the counted jumps reach: count one more. The loop is about to move on to positions that the counted jumps cannot reach, and the last position is still ahead. So one more jump is needed, whichever path you take.
- With that jump you reach as far as the farthest spot noted. The extra jump may start from any position visited so far, so it can land anywhere up to the farthest spot noted. Claiming all of that is safe: a longer reach can do everything a shorter one can.
- Return the number of jumps counted. Each jump was counted only when positions ahead could not be reached without one more, and each reach went as far as possible. So the count is the fewest jumps that arrive at the last position.
The same numbered steps are the comments in each solution.
JavaScript
function solve(nums) {
// 1. Start with 0 jumps, which reach position 0 and no farther
let end = 0,
farthest = 0,
jumps = 0;
// 2. Visit every position except the last one
for (let i = 0; i < nums.length - 1; i++) {
// 3. Note the farthest spot that one more jump could land on
farthest = Math.max(farthest, i + nums[i]);
// 4. This is the last position the counted jumps reach: count one more
if (i === end) {
jumps++;
// 5. With that jump you reach as far as the farthest spot noted
end = farthest;
}
}
// 6. Return the number of jumps counted
return jumps;
}TypeScript
function solve(nums: number[]): number {
// 1. Start with 0 jumps, which reach position 0 and no farther
let end = 0,
farthest = 0,
jumps = 0;
// 2. Visit every position except the last one
for (let i = 0; i < nums.length - 1; i++) {
// 3. Note the farthest spot that one more jump could land on
farthest = Math.max(farthest, i + nums[i]);
// 4. This is the last position the counted jumps reach: count one more
if (i === end) {
jumps++;
// 5. With that jump you reach as far as the farthest spot noted
end = farthest;
}
}
// 6. Return the number of jumps counted
return jumps;
}
Python
def solve(nums):
# 1. Start with 0 jumps, which reach position 0 and no farther
end = farthest = jumps = 0
# 2. Visit every position except the last one
for i in range(len(nums) - 1):
# 3. Note the farthest spot that one more jump could land on
farthest = max(farthest, i + nums[i])
# 4. This is the last position the counted jumps reach: count one more
if i == end:
jumps += 1
# 5. With that jump you reach as far as the farthest spot noted
end = farthest
# 6. Return the number of jumps counted
return jumps
Example 2, traced
Input nums = [2,3,0,1,4] Output 2
| Position | Longest jump from it lands on | Farthest spot noted | Last position the counted jumps reach? | Jumps counted, and their reach |
|---|---|---|---|---|
| 0 | 0 + 2 = 2 | 2 | Yes: 0 jumps reach up to position 0 | 1 jump, reaching up to position 2 |
| 1 | 1 + 3 = 4 | 4 | No: 1 jump reaches up to position 2 | 1 jump, reaching up to position 2 |
| 2 | 2 + 0 = 2 | 4 | Yes: 1 jump reaches up to position 2 | 2 jumps, reaching up to position 4 |
| 3 | 3 + 1 = 4 | 4 | No: 2 jumps reach up to position 4 | 2 jumps, reaching up to position 4 |
| 4, the last position | - | - | Not visited | Return 2 |
Why it works
Reaching farther never costs anything: a stretch that ends later contains every position of one that ends earlier, so it offers every jump the shorter one offers. So always taking the farthest reach and never reconsidering, the greedy choice, is safe. A jump is counted only when the positions ahead cannot be reached without it, and each stretch ends as far as possible, so no smaller count can arrive.
Time and space
Time O(n), extra space O(1). The loop makes one pass: it visits each position except the last once, with one comparison to update the farthest spot and one check for the end of the reach, so the time is O(n). Three numbers are stored, whatever the length of the list: O(1) space.
n and m are input sizes, h is a tree's height, V and E are a graph's vertices and edges. The bounds describe this reference solution.
Watch out
- Stop the loop before the last position. If it is visited too, an extra jump is counted whenever the reach ends exactly there: example 1 returns 3, not 2, and a list with one number returns 1, not 0.
- Do not always take the longest jump. In example 1 that goes from position 0 to 2, then to 3, then to 4: three jumps. The short jump to position 1 is better: one jump from there reaches the end.
- If you choose each landing spot yourself, compare position plus number, not the number. In 3, 3, 1, 2, 1, 1, going through the 3 at position 1 takes three jumps, and through the 2 at position 3 only two.
Pattern
Greedy choice. Builds on Jump Game.
Keep going
8 more problems solved with greedy choice
- Maximum SubarrayMedium
- Jump GameMedium
- Gas StationMedium
- Hand of StraightsMedium
- Merge Triplets to Form Target TripletMedium
- Partition LabelsMedium
- Valid Parenthesis StringMedium
- Non Overlapping IntervalsMedium