Problem overview
You stand on the first index of an IntArray named nums. The value nums[i] is the longest jump you can make from index i, and you may also jump any shorter distance, as long as it's at least 1. jump returns the minimum number of jumps needed to reach the last index.
For [2, 3, 1, 1, 4] the answer is 2: jump 1 step from index 0 to index 1, then 3 steps to the last index. Starting with a 2-step jump to index 2 would need three jumps in total.
Approach
Ask the question from each cell: how many jumps does it take to reach the end starting here? Call that minJumps(nums, i). Then:
- From the last cell the answer is 0, because we are already there.
- From any other cell
i, the first jump lands on some celljbetweeni + 1andi + nums[i], and then we continue fromj. That costs1 + minJumps(nums, j). The best choice ofjgives the answer.
This is backtracking in its simplest form: try every first jump, solve the smaller problem that remains, and keep the smallest total. The main function just asks the question for cell 0.
This lesson is the plain recursive version. It repeats work, because the same cell is reached through many different paths and its answer is computed again every time. Later solutions to this problem remove that repetition. Here the goal is to get the recursive idea right first.
Step by step
- Use the helper.
jumpreturnsminJumps(nums, 0), because every journey starts at the first cell. - Declare
minJumpswith the array and an indexi, returning anInt. - Base case. If
i == nums.size - 1, return 0. Every recursive call moves to a larger index, so the calls always reach a stopping point. - Start with a large result.
var result = nums.sizeacts as infinity. Any real path needs at mostnums.size - 1jumps, so any real path beats it. If no cell is reachable fromi,resultstays this large, which marks a dead end. - Loop over the possible landing cells.
jstarts ati + 1and the loop runs whilej < nums.sizeandj <= i + nums[i]. The first condition keeps us inside the array, the second respects the jump length. - Try each jump. Update
resultwithminOf(result, 1 + minJumps(nums, j)): one jump tojplus the best continuation from there. - Return
resultonce every first jump has been tried.
The solution in Kotlin
class Solution {
fun minJumps(nums: IntArray, i: Int): Int {
if (i == nums.size - 1) {
return 0
}
var result = nums.size
var j = i + 1
while (j < nums.size && j <= i + nums[i]) {
result = minOf(result, 1 + minJumps(nums, j))
j++
}
return result
}
fun jump(nums: IntArray): Int {
return minJumps(nums, 0)
}
}
Kotlin details:
minOf(a, b)is a standard function that returns the smaller of two values. It works forIntand other comparable types.varforresultandj. Both change during the loop. Avalcan't be reassigned, and the compiler would rejectresult = ....whileand&&. The second condition is checked only if the first is true. Becausej < nums.sizecomes first, the code never reads outside the array in the loop condition itself.- Return in the base case. An
Intfunction must return a value on every path, and here every path ends in areturn.
Following the example
For [2, 3, 1, 1, 4], minJumps(nums, 0) may jump to index 1 or 2. From index 1 it can jump to index 2, 3 or 4, and the jump to index 4 is the last cell, which costs 1 + 0, so minJumps(nums, 1) is 1. Then the jump from index 0 to index 1 costs 1 + 1 = 2. The jump from index 0 to index 2 leads to index 3 and then index 4, which costs 3. The minimum is 2.
Complexity
The lesson states O(n²) time and O(n) space. The space part is right: the recursion goes at most n levels deep, since every call moves to a larger index. The time is a different matter for this version. It has no memory, so it recomputes the same cells again and again. On an array like [4, 3, 2, 1, 0], where every cell can reach every later cell, the number of calls doubles with each extra cell: 16 calls for 5 cells, 512 for 10, 16384 for 15, 524288 for 20 and 16777216 for 25. That is exponential, O(2ⁿ) in the worst case.
On friendlier inputs it is fast. For an array of twenty 1s, each cell has only one choice and there are just 20 calls. The faster solutions to this problem keep the same recursive idea and avoid the repetition.
Edge cases and common mistakes
- A single cell.
jump(intArrayOf(0))is 0, because the start is already the last index. - Zeros. A cell holding 0 allows no jump, so its loop doesn't run and it returns
nums.size. For[1, 0]the answer is 1, because the last cell is reachable from the first. - Unreachable end. For
[3, 2, 1, 0, 4]the last cell can't be reached. The function returns 5, which isnums.size, the value that stands for infinity here, not a real jump count. Inputs where the end is always reachable don't hit this case. - Using
<instead of<=in the loop. Then the longest jumpnums[i]is never tried, and the answer for[2, 3, 1, 1, 4]becomes 3 instead of 2, because the 3-step jump from index 1 is lost. - Forgetting the array bound. Without
j < nums.size, a largenums[i]sendsjpast the end andnums[j]throwsArrayIndexOutOfBoundsException. - Starting
jati. A jump of length 0 would recurse on the same cell forever. - Greedy shortcuts. Always taking the longest jump isn't correct: in
[2, 3, 1, 1, 4]it picks index 2 and needs three jumps. Trying every option is what makes this version correct.
Testing
[2, 3, 1, 1, 4]returns 2.[0]returns 0.[1, 1, 1, 1]returns 3.[1, 0]returns 1.