Problem overview
The method jump receives an array nums of non-negative integers. You start at index 0, and nums(i) is the longest jump allowed from index 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: one step from index 0 to index 1, then three steps to the last index. The statement asks for an iterative solution in O(n^2) time and O(n) extra space.
Approach: fill the table from right to left
The recursive memoized solution defines the answer for cell i in terms of cells to its right. Its first jump goes to some cell j with i < j <= i + nums(i), and the cost is 1 plus the answer for j. The iterative version uses the same relationship but fills a table instead of recursing.
Let dp(i) be the minimum number of jumps from cell i to the last cell. The last cell needs 0 jumps. Every other dp(i) depends only on cells further right, so if you walk i from the second-to-last cell down to 0, every value you need is already final. There is no recursion, no cache lookups and no risk of a deep call stack.
The table starts filled with n, the array length. A real path needs at most n - 1 jumps, so n acts as infinity: any real path beats it.
Here is the table for nums = [2, 3, 1, 1, 4], so n = 5. It starts as [5, 5, 5, 5, 0]. At i = 3 the value is 1, so only cell 4 is reachable and dp(3) becomes 1 + 0 = 1. At i = 2 the only reachable cell is 3, giving dp(2) = 2. At i = 1 the value is 3, so cells 2, 3 and 4 are reachable. The values of 1 + dp(j) are 3, 2 and 1, and the best is jumping straight to cell 4, so dp(1) = 1. At i = 0 cells 1 and 2 are reachable; 1 + dp(1) = 2 beats 1 + dp(2) = 3, so dp(0) = 2.
Step by step
- Store the length in
n, and declaredpasArray.fill(n)(n): an array of sizenwith every entry set ton. - Set
dp(n - 1)to 0. You are already on the last cell, so no jumps are needed. - Loop
iovern - 2 to 0 by -1, which counts down and includes 0. Walking right to left guarantees that the cellsidepends on are final. - Visit the reachable cells with
var j = i + 1and awhileloop running whilej < nandj <= i + nums(i), ending its body withj += 1. - Relax
dp(i): a first jump tojcosts1 + dp(j), so keep the smaller of that and the currentdp(i)usingmath.min. - Finish with
dp(0)as the last expression. It is the answer from the starting cell.
The solution in Scala
class Solution {
def jump(nums: Array[Int]): Int = {
val n = nums.length
val dp = Array.fill(n)(n)
dp(n - 1) = 0
for (i <- n - 2 to 0 by -1) {
var j = i + 1
while (j < n && j <= i + nums(i)) {
dp(i) = math.min(dp(i), 1 + dp(j))
j += 1
}
}
dp(0)
}
}
The Scala details worth a closer look:
Array.fill(n)(n)has two parameter lists: the first is the number of elements, the second is the value to put in each. It creates a newArray[Int]of sizenfilled withn.- Counting down.
n - 2 to 0 by -1is aRangethat includes both ends and steps by -1. Whenn - 2is below 0 the range is empty and the loop body never runs.untilwould exclude 0, which is why the loop usesto. - Arrays are mutable and fixed-size.
dp(i) = ...updates an element in place. Indexing uses parentheses, anddp(n - 1) = 0is a call to the array'supdatemethod. - The last expression is the result. There is no
return;dp(0)is the value of the method body. - Ordinary loops. The
whileloop is needed because its upper bound,i + nums(i), is combined with an array-length check. Aforoveri + 1 to math.min(n - 1, i + nums(i))would be the same.
Complexity
Time: O(n^2). The outer loop visits n - 1 cells, and for each the inner loop looks at up to n - 1 reachable cells. Each step does constant work.
Space: O(n). Only the dp array grows with the input. Compared with the memoized recursion, it stores the same n answers without a map and without recursion frames.
Edge cases and common mistakes
- A single element. For
[0],n - 2is -1, so the loop doesn't run anddp(0)is the 0 set for the last cell. The answer is 0. - The example.
[2, 3, 1, 1, 4]returns 2. - All ones.
[1, 1, 1, 1]returns 3. - A jump bigger than the array. For
[5, 0, 0, 0, 0, 0], the result is 1. Thej < ntest keeps the loop from reading past the end for larger numbers. - Unreachable end. In
[1, 0, 3]the last cell can't be reached. The method returnsn, here 3, becausedp(0)keeps the "infinity" value. The lesson's tests don't use such arrays. - Going left to right. Filling
dpfrom index 0 would read entries that are still at their starting valuen, giving wrong answers. - Starting the table at 0. If
dpwere filled with zeros,math.minwould always keep 0 and the answer would be 0.
Testing
[2, 3, 1, 1, 4]gives2.[0]gives0.[1, 1, 1, 1]gives3.[5, 0, 0, 0, 0, 0]gives1.[2, 0, 1]gives1.
