You are given an integer array nums where each element represents your maximum jump length from that position. You start at index 0. The goal is to determine if you can reach the last index.
Why Greedy? This problem can be solved with Dynamic Programming, tracking reachable states. However, it is fundamentally a question of “reachability.” We don’t need to know how to get to the end, just if we can.
At any given point, the best strategy is simply to keep track of the maximum reachable index we’ve seen so far. As we iterate through the array, if our current index is within our “reachable zone”, we can update our max reachable index. If we encounter an index that is greater than our max reachable index, it means we are stuck in a hole (behind a zero) and cannot proceed, so we return false.
An alternative intuitive greedy approach is working backwards: trying to shift the “goalpost” from the end of the array to the beginning.
Specific questions to practice:
- Jump Game II
- Jump Game III
- Gas Station
Use backtracking with memoization. From index 0, try all possible jumps from 1 to nums[0]. Recursively check if any lead to the end.
Complexity: time. Space for memoization array. Will TLE on LeetCode.
Track the maximum reach. Pseudocode logic:
- Initialize
maxReach = 0. - Loop
ithroughnums:- If
i > maxReach: returnfalse(we can’t even reach this current step). maxReach = max(maxReach, i + nums[i]).- If
maxReach >= nums.size() - 1: returntrue(early exit optimization).
- If
- Return
true.
This is often considered even more intuitive. Pseudocode logic:
- Set
goal = nums.size() - 1. - Loop
ibackwards fromnums.size() - 2down to 0:- If from index
iwe can reach thegoal(i.e.,i + nums[i] >= goal):- The new goal is now
i. We just need to reachinow.
- The new goal is now
- If from index
- At the end, if
goal == 0, returntrue.
// Optimal Approach 1: Forward Greedy
class Solution {
public:
bool canJump(std::vector<int>& nums) {
int maxReach = 0;
for (int i = 0; i < nums.size(); ++i) {
// If the current index is beyond our maximum reach, we're stuck
if (i > maxReach) {
return false;
}
// Update the maximum reachable index
maxReach = std::max(maxReach, i + nums[i]);
// Optimization: If we can already reach the end, stop early
if (maxReach >= nums.size() - 1) {
return true;
}
}
return true;
}
};
// Optimal Approach 2: Backward Greedy (For reference)
/*
class Solution {
public:
bool canJump(std::vector<int>& nums) {
int goal = nums.size() - 1;
for (int i = nums.size() - 2; i >= 0; --i) {
if (i + nums[i] >= goal) {
goal = i;
}
}
return goal == 0;
}
};
*/- Time Complexity:
- We traverse the array exactly once.
- Space Complexity:
- We only use a single integer variable (
maxReachorgoal).
- We only use a single integer variable (
- Single element array:
[0]. Loop starts.maxReach = 0,i = 0.maxReach >= 0returns true. Correct, you are already at the end. - Trapped by zero:
[3, 2, 1, 0, 4].i=0, reach=3i=1, reach=3i=2, reach=3i=3, reach=3i=4, reach=3. Herei=4 > maxReach (3), returns false. Correct.
- Recognition: “Can you reach the end” + “Array of steps/jumps”. Instead of calculating every path (DP), just track the bounding box of your reach (Greedy).
- Mental Model:
- Forward: Imagine you have a tank of gas. Each cell offers to refill your tank to
nums[i], but you only take it if it gives you more gas than you currently have. If your gas runs out (i > maxReach), you stop. - Backward: Imagine trying to get home (index ). You walk backward and find a bus stop at index that drops you off at home. Great! Now your new objective is just to get to that bus stop. You keep moving your destination closer to your starting point.
- Forward: Imagine you have a tank of gas. Each cell offers to refill your tank to