Problem overview
The method jump receives an Array[Int] called nums. You start at index 0, and nums(i) is the longest jump you can make from index i: you may move to any index from i + 1 to i + nums(i). The method returns the minimum number of jumps needed to reach the last index.
For [2, 3, 1, 1, 4] the answer is 2: jump one step from index 0 to index 1, then three steps to index 4. No single jump can do it, because nums(0) is 2 and the last index is 4.
Approach: try every first jump
Ask a smaller question: what is the minimum number of jumps to the end if you are standing at index i? Call the answer minJumps(nums, i). Then:
- if
iis the last index, the answer is 0; - otherwise, you must choose a first jump to some index
jini + 1toi + nums(i). That jump costs 1, and fromjyou needminJumps(nums, j)more. The answer is the best such choice.
The recursion always moves to a larger index, so it always ends at the last index (or at a cell with nothing reachable).
This lesson is the starting point for the problem. It tries every possibility, which is simple but slow: the same index is solved again and again. The other lessons on this problem build on it to reach the lower complexity.
Step by step
- Call the helper from
jump. The answer begins at index 0, so the body is justminJumps(nums, 0). As the final expression, its value is returned without areturn. - Declare
minJumps(nums, i)with anArray[Int]and anInt, returning anInt. - Base case: if
i == nums.length - 1, return 0. - Start the best answer at
nums.length. No real path needs that many jumps (the most possible isnums.length - 1), so it acts as infinity. Declare it as avar, because it will be updated. - Loop over the reachable cells. With
var j = i + 1, awhileloop runs as long asj < nums.lengthandj <= i + nums(i), and ends each pass withj += 1. - Update the best answer:
result = math.min(result, 1 + minJumps(nums, j)). One jump tojplus the best from there. - Return
resultafter the loop, as the final expression.
The solution in Scala
class Solution {
def minJumps(nums: Array[Int], i: Int): Int = {
if (i == nums.length - 1) {
return 0
}
var result = nums.length
var j = i + 1
while (j < nums.length && j <= i + nums(i)) {
result = math.min(result, 1 + minJumps(nums, j))
j += 1
}
result
}
def jump(nums: Array[Int]): Int = {
minJumps(nums, 0)
}
}
Scala details:
- Two conditions in the loop.
j < nums.lengthkeeps the loop inside the array.j <= i + nums(i)keeps it inside the jump length. Without the first one, a bignums(i)near the end would read past the array and throw. math.minisscala.math.min, reachable asmath.minwithout an import. It works onIntvalues.returnonly where needed. The base case uses an earlyreturn. The last expression,result, is the method's value.varversusval.resultandjchange, so they arevars.- The big initial value. If no cell is reachable from some index,
resultkeeps its starting valuenums.length. The caller then computes1 + nums.length, which is still larger than any real answer, so such a dead end never wins a comparison against a real path. It also can't overflow the wayInt.MaxValue + 1would.
Complexity
Time: exponential. Every call of minJumps spawns calls for each reachable index, and nothing is remembered. If every value is large, the number of calls doubles for each extra cell. For an array of n values, all equal to n, there are 512 calls when n is 10, 16,384 when n is 15, 524,288 when n is 20 and 8,388,608 when n is 24. In these cases the count is 2^(n - 1).
Space: O(n). The recursion goes at most n calls deep, since each call moves to a larger index.
The calls repeat work because many paths reach the same index. Remembering the answer per index brings the time down to O(n²): at most n answers, each found by a loop over at most n cells.
Edge cases and common mistakes
- Single cell.
[0]returns 0, because you are already at the last index. - Steps of 1.
[1, 1, 1, 1]returns 3. With all ones, each call has one reachable cell, so the work stays small: 24 calls for 24 values. - Unreachable end. This version has no special value for "impossible". For
[3, 2, 1, 0, 4], every path gets stuck on the 0 at index 3, and the method returns 5, which isnums.length, not-1. For[0, 1]it returns 2. A result ofnums.lengthor more means the last index can't be reached. - Forgetting the length check. A loop condition of only
j <= i + nums(i)runs off the end of the array. - Starting
jati. A jump of length 0 would make the function call itself with the same index forever. - Taking the largest jump. Always jumping as far as possible is not always optimal. In
[2, 3, 1, 1, 4]the best first jump is 1 step, not 2: jumping to index 2 (value 1) needs two more jumps, three in total.
Testing
[2, 3, 1, 1, 4]gives2.[0]gives0.[1, 1, 1, 1]gives3.[5, 5, 5, 5, 5, 5]gives1.
