Intuition
When you first see this problem, your mind might jump (pun intended) to graph traversal algorithms like Breadth-First Search (BFS) or Depth-First Search (DFS). You could think of each array index as a node and each possible jump as an edge. You could then explore all possible paths from the start to see if any of them land on the final index.
This would work, but it might be overkill. Exploring every path can be computationally expensive, especially if the jump lengths are large. This often leads to a “Time Limit Exceeded” error on platforms like LeetCode.
So, let’s reframe the question. Instead of asking “What path should I take?”, let’s ask a simpler, more powerful question: “What’s the farthest I can possibly reach at any given moment?”
This shift in perspective is the key. We don’t need to map out the entire journey. We just need to be optimistic and keep track of our maximum possible reach. As long as we never get to a point where the next step is beyond our reach, we’re still in the game. This is the essence of a greedy approach.
Approach
Our greedy strategy will be to iterate through the array, maintaining a variable that tracks the maximum index we can reach. Let’s call it max_reach.
- Initialize
max_reach = 0. At the beginning, the farthest we can reach is our starting position, index 0. - Iterate through the array from left to right, with an index
i. - At each index
i, we first perform a critical check: Can we even get here? We can only be at indexiif it’s within our currentmax_reach. Ifi > max_reach, it means we’ve encountered a gap of zeros (or small numbers) that we couldn’t jump over. We are stuck, and it’s impossible to proceed. In this case, we can immediately returnfalse. - If we can reach index
i, we then calculate the potential new farthest reach from this spot. From indexi, we can jump up tonums[i]steps, reaching as far asi + nums[i]. - We update our global
max_reachto be the maximum of its current value and this new potential reach. This is our greedy choice: we’re always updating our horizon to the farthest possible point. The line would look like:max_reach = max(max_reach, i + nums[i]). - If our
max_reachever touches or surpasses the last index of the array, we know it’s possible to win. We can stop and returntrue.
If we complete the loop without ever getting stuck (i.e., the i > max_reach check never fails), it implies we were able to successfully reach the last element.
Let’s trace this with nums = [3, 2, 1, 0, 4]:
- Start:
max_reach = 0,last_index = 4. i = 0:iis not greater thanmax_reach(0 is not > 0). From here, we can reach0 + nums[0] = 3. Updatemax_reach = max(0, 3) = 3.i = 1:iis not greater thanmax_reach(1 is not > 3). From here, we can reach1 + nums[1] = 3. Updatemax_reach = max(3, 3) = 3.i = 2:iis not greater thanmax_reach(2 is not > 3). From here, we can reach2 + nums[2] = 3. Updatemax_reach = max(3, 3) = 3.i = 3:iis not greater thanmax_reach(3 is not > 3). From here, we can reach3 + nums[3] = 3. Updatemax_reach = max(3, 3) = 3.i = 4:iis greater thanmax_reach(4 > 3). We’ve reached a point that is beyond our horizon. We can’t get here. Returnfalse.
This simple, linear scan gives us the correct answer efficiently.
Complexity
-
Time complexity:
O(n)
We do a single pass through the array from the beginning to the end. Therefore, the time complexity is linear with respect to the number of elements in the input array.
-
Space complexity:
O(1)
We only use a few variables to keep track of our state (max_reach, the loop index i, etc.), regardless of the size of the input array. This means our space usage is constant.
Code
Python
from typing import List
class Solution:
def canJump(self, nums: List[int]) -> bool:
"""
Determines if the last index can be reached using a greedy approach.
"""
max_reach = 0
n = len(nums)
# Iterate through the array indices.
# We only need to iterate as long as our current position `i`
# is within our `max_reach`.
for i in range(n):
# If the current index `i` is greater than the farthest we could have reached,
# it means we are stuck and can't proceed.
if i > max_reach:
return False
# Update the maximum reach based on the jump power from the current index.
# This is the greedy choice: always extend the horizon as much as possible.
max_reach = max(max_reach, i + nums[i])
# An optional optimization: if our reach already covers the goal, we're done.
if max_reach >= n - 1:
return True
# If the loop completes, it means the last index was reachable.
# This line is only reached if the optimization is not hit, for example,
# in an array like [0] or [1].
return True
How to recognize similar problems
This greedy pattern is quite common. You can suspect a similar approach is needed when a problem has these characteristics:
- Array Traversal with Rules: The problem involves moving through an array where the value at an index dictates the rules of movement (e.g., how far you can jump, where you can go next).
- Possibility vs. Optimality: The question asks “Is it possible?” rather than “What is the best way?“. For example, “Jump Game II” asks for the minimum number of jumps, which requires a slightly more complex approach (often BFS). “Can you reach the end?” is a classic sign for this greedy reachability pattern.
- Forward-Looking Decisions: The choice at your current position affects the range of future choices. The greedy strategy simplifies this by consolidating all future choices into a single, optimistic metric: the farthest possible reach.
How to remember the concept
A great way to remember this concept is the “Horizon Analogy”:
Imagine you are a traveler. The
max_reachvariable is your horizon—the farthest point you know you can get to. You start walking forward (for i in ...). With every step you take, you check if you are still within your known horizon (if i > max_reach). If you step past it, you’re lost in an unreachable land. From every spot you stand on, you look ahead with your binoculars (i + nums[i]) and update your horizon to the new farthest point you can see. Your goal is simply to ensure your horizon eventually extends to or beyond your destination.
This analogy captures the core idea: keep moving forward as long as you’re on safe ground, and always be optimistic about how far you can go next.