Problem overview
The function jump receives an IntArray of non-negative integers. You start at index 0, and each value is the maximum jump length from that position. The function 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 from index 1 to the last index. The statement asks for an iterative solution in O(n^2) time and O(n) extra space, so this lesson turns the memoized recursion into loops.
Approach: fill a table from right to left
The recursive solution defines the answer for a cell in terms of the cells to its right: from cell i, a first jump lands on some cell j between i + 1 and i + nums[i], and the rest costs whatever cell j costs. The memoized version fills its cache on demand, in the order the recursion happens to visit cells.
The iterative version fills a table in a fixed order, so no recursion is needed. Let dp[i] be the minimum number of jumps from cell i to the last cell. The last cell needs 0 jumps. Every other entry depends only on entries to its right, so if the loop runs from right to left, every entry it needs is already final when it is read. Then dp[i] is the minimum of 1 + dp[j] over all reachable cells j, and the answer is dp[0].
The cost matches the memoized version: a table of n entries, each computed with a loop over at most n cells. What changes is that the call stack no longer grows with the input.
On [2, 3, 1, 1, 4] the table fills as follows, starting from the right. dp[4] = 0. Cell 3 can reach cell 4, so dp[3] = 1. Cell 2 can reach cell 3, so dp[2] = 2. Cell 1 can reach cells 2, 3 and 4, and the best is 1 + dp[4] = 1. Cell 0 can reach cells 1 and 2, and the best is 1 + dp[1] = 2, so dp[0] = 2.
Step by step
- Store the length in
nand createdp, anIntArrayof sizen. Initialise every entry tonwith a lambda. This is larger than any real answer (at mostn - 1jumps), so it acts as infinity until a real path is found. - Set
dp[n - 1]to 0. The last cell needs no jumps. - Loop from
n - 2down to 0 with an indexi. Going right to left guarantees that the cells to the right are final. - Loop over the landing cells. Inside, start
jati + 1and continue whilej < nandj <= i + nums[i], increasingjat the end of the body. - Improve
dp[i]. A first jump fromitojcosts 1 plusdp[j], so setdp[i]to the smaller of its current value and1 + dp[j]. - Return
dp[0]once both loops finish, since the answer starts at the first cell.
The solution in Kotlin
class Solution {
fun jump(nums: IntArray): Int {
val n = nums.size
val dp = IntArray(n) { n }
dp[n - 1] = 0
for (i in n - 2 downTo 0) {
var j = i + 1
while (j < n && j <= i + nums[i]) {
dp[i] = minOf(dp[i], 1 + dp[j])
j++
}
}
return dp[0]
}
}
The Kotlin details worth a closer look:
- The initializer lambda.
IntArray(n) { n }calls the lambda once per position to compute the starting value. This lambda ignores the index and always givesn. Without it,IntArray(n)would fill the array with zeros, which would look like "no jumps needed" for every cell. downTo.n - 2 downTo 0counts down and includes 0. A plain0..n - 2counts upwards and would readdp[j]before it is final. Whennis 1, the range-1 downTo 0is empty, so the loop body never runs.minOf. This built-in function returns the smaller of its arguments, and the compiler picks the version forInt. It is the idiomatic replacement forMath.min.- Arrays are mutable.
val dpfixes the reference, anddp[i] = ...changes an element. The array itself is not copied or reassigned.
Complexity
Time: O(n^2). The outer loop runs n - 1 times and the inner loop visits at most n - 1 cells for each one. In the worst case, such as every value being large, the total is about n^2 / 2 steps.
Space: O(n). The only extra memory is the dp array. There is no recursion, so there is no call stack to grow with the input.
Edge cases and common mistakes
- One element.
[7]returns 0: the loop does not run, anddp[0]is the last cell, already set to 0. - An empty array.
dp[n - 1]would bedp[-1], which throws anArrayIndexOutOfBoundsException. The lesson assumes at least one element. - Going left to right. Reading
dp[j]before it is computed gives the placeholdern, and the result is wrong. - Initialising with 0. A zero-filled table lets
minOfkeep the 0 and return 0 for everything. - Unreachable end. For
[1, 0, 5]the function returns 3, which is the infinity placeholder leaking through, not a count of jumps. The lesson assumes the last index is reachable. - Off-by-one at the reach. The farthest cell is
i + nums[i], inclusive, so the condition is<=.
Testing
[2, 3, 1, 1, 4]gives2.[7]gives0.[3, 1, 1, 1]gives1.[2, 0, 1, 1]gives2.[1, 1, 1, 1]gives3.