LeetCode 55: Jump Game
A clear guide to solving Jump Game with greedy reachability.
Problem Restatement
We are given an integer array nums.
We start at index 0. Each value nums[i] tells us the maximum number of steps we can jump forward from index i.
We need to return true if we can reach the last index. Otherwise, return false.
The official constraints are 1 <= nums.length <= 10^4 and 0 <= nums[i] <= 10^5.
Input and Output
| Item | Meaning |
|---|---|
| Input | An integer array nums |
| Output | true if the last index is reachable, otherwise false |
| Start | Index 0 |
| Jump rule | From index i, we may jump at most nums[i] steps forward |
Function shape:
def canJump(nums: list[int]) -> bool:
...
Examples
For:
nums = [2, 3, 1, 1, 4]
The answer is:
True
One valid path is:
index 0 -> index 1 -> index 4
At index 0, nums[0] = 2, so we can jump to index 1.
At index 1, nums[1] = 3, so we can jump to the last index.
For:
nums = [3, 2, 1, 0, 4]
The answer is:
False
We can reach index 3, but nums[3] = 0.
From index 3, we cannot move forward, so index 4 cannot be reached.
First Thought: Try Every Jump
A direct approach is to try every possible jump from every reachable index.
For example, from index i, we can try:
i + 1
i + 2
...
i + nums[i]
Then recursively check whether any of those choices can reach the last index.
This approach is natural, but it can repeat a lot of work. Many different paths may reach the same index, and then solve the same subproblem again.
Key Insight
We do not need to remember every path.
We only need to remember the farthest index we can reach so far.
Call this value:
farthest
As we scan the array from left to right:
- If the current index is greater than
farthest, we cannot even stand on this index. - If we can stand on this index, then we can use
nums[i]to possibly extendfarthest.
The update is:
farthest = max(farthest, i + nums[i])
If at any point farthest reaches or passes the last index, the answer is True.
Algorithm
Initialize:
farthest = 0
Then scan each index i.
For each index:
- If
i > farthest, returnFalse. - Update
farthestwithi + nums[i]. - If
farthest >= len(nums) - 1, returnTrue.
If the loop finishes, return True.
Correctness
At any point during the scan, farthest means the largest index reachable using only positions we have already processed.
Initially, only index 0 is reachable, so farthest = 0.
When we process index i, there are two cases.
If i > farthest, then index i cannot be reached from any earlier position. Since we scan from left to right, every later index is even farther away. Therefore the last index cannot be reached, and returning False is correct.
If i <= farthest, then index i is reachable. From index i, we may jump as far as i + nums[i]. Updating farthest with the maximum of the old value and this new reach preserves the meaning of farthest.
If farthest >= len(nums) - 1, the last index is reachable, so returning True is correct.
Therefore the algorithm returns True exactly when the last index can be reached.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n) |
We scan the array once |
| Space | O(1) |
We keep only one integer |
Implementation
class Solution:
def canJump(self, nums: list[int]) -> bool:
farthest = 0
last = len(nums) - 1
for i, jump in enumerate(nums):
if i > farthest:
return False
farthest = max(farthest, i + jump)
if farthest >= last:
return True
return True
Code Explanation
We start with:
farthest = 0
This means we can reach index 0 at the beginning.
We also store the last index:
last = len(nums) - 1
Then we scan each index and jump value:
for i, jump in enumerate(nums):
Before using nums[i], we must confirm that index i is reachable:
if i > farthest:
return False
If i is reachable, then from i we may reach up to:
i + jump
So we update:
farthest = max(farthest, i + jump)
Once farthest reaches the last index, we can stop immediately:
if farthest >= last:
return True
The final return handles the case where the loop completes normally:
return True
Testing
def run_tests():
s = Solution()
assert s.canJump([2, 3, 1, 1, 4]) is True
assert s.canJump([3, 2, 1, 0, 4]) is False
assert s.canJump([0]) is True
assert s.canJump([0, 1]) is False
assert s.canJump([2, 0, 0]) is True
assert s.canJump([1, 0, 1, 0]) is False
assert s.canJump([4, 0, 0, 0, 0]) is True
print("all tests passed")
run_tests()
| Test | Why |
|---|---|
[2,3,1,1,4] |
Reachable standard example |
[3,2,1,0,4] |
Blocked by zero |
[0] |
Already at the last index |
[0,1] |
Cannot move from the first index |
[2,0,0] |
Jump can pass over zeros |
[1,0,1,0] |
Gets stuck before the end |
[4,0,0,0,0] |
One jump can reach the last index |