Jump Game
You are given an integer array nums. You start at index 0, and nums[i] is the maximum number of positions you may jump forward from index i — you may jump any distance from 0 up to nums[i]. Return true if you can reach the last index by some sequence of jumps, and false otherwise. A value of 0 means you cannot move forward from that index at all.
- 1 ≤ nums.length ≤ 10⁴
- 0 ≤ nums[i] ≤ 10⁵
- You always start at index 0
- From index i you may land on any index from i+1 up to i + nums[i], if it exists
nums = [2, 3, 1, 1, 4]truenums = [3, 2, 1, 0, 4]falsenums = [0]truenums = [2, 0, 0]truenums = [1, 0, 1]falseEach index gives you a maximum jump length. From index i with value 3, you may step to i+1, i+2, or i+3 — your choice. Can you, by some series of such steps, land on the final index? A single 0 is a wall you cannot step over from where you stand, but you can often jump over it if you build up enough reach earlier.
We only want a yes/no reachability answer, not the number of jumps or the path. That "yes/no" is a hint: reachability problems often collapse to tracking a single number.
The literal reading invites recursion: from each index, try every jump length and see if any leads home.
def can_reach(i, nums):
if i >= len(nums) - 1:
return True
for jump in range(1, nums[i] + 1):
if can_reach(i + jump, nums):
return True
return FalseThis branches enormously — each index fans out into many, and the same indices get re-explored down countless paths, giving exponential time. Memoising it would bring it down to O(N²), the DP version, but there is a property here that makes even that unnecessary.
The insight is to stop asking "which sequence of jumps?" and ask only: what is the furthest index I could possibly be standing on by now? Call it reach.
Walk left to right. At each index i, first check whether i is even reachable — that is, whether i <= reach. If it is not, then no earlier index could launch you as far as i, so there is a gap you cannot cross, and the answer is false. If i is reachable, then from i you can extend your frontier to i + nums[i], so update reach to the larger of its current value and i + nums[i]. The moment reach reaches or passes the last index, the answer is true.
Why is it safe to collapse all those paths into one number? Because reachability is downward closed: if you can reach index i, you can reach every index between your start and i too (just jump shorter). So the entire set of currently-reachable indices is always a solid prefix 0..reach with no holes, and a single number describes it completely. That is the property the yes/no phrasing was hinting at.
def can_jump(nums):
reach = 0
for i in range(len(nums)):
if i > reach: # this index is unreachable: a gap we cannot cross
return False
reach = max(reach, i + nums[i])
if reach >= len(nums) - 1:
return True
return TrueOne pass, O(N) time, O(1) space. This is a greedy: at every step we make the locally maximal extension of the frontier and never reconsider, and it is safe precisely because a wider frontier can never hurt a reachability question.
And the failing case, [3, 2, 1, 0, 4]:
- i=0: reach = max(0, 3) = 3.
- i=1: 1 ≤ 3. reach = max(3, 1+2) = 3.
- i=2: 2 ≤ 3. reach = max(3, 2+1) = 3.
- i=3: 3 ≤ 3. reach = max(3, 3+0) = 3. The frontier is stuck at 3.
- i=4: 4 > reach(3) → false. The 0 at index 3 is a wall, and nothing before it reached far enough to leap over.
Two transferable ideas.
When a problem asks whether something is reachable, look for a single frontier value to carry forward instead of enumerating paths. It works whenever reachability is monotonic — being able to reach far implies being able to reach everything nearer. That "solid prefix" structure is what turns an exponential search into one greedy pass.
A wider frontier is always at least as good, so greedily maximising it at each step is safe with no exchange argument gymnastics needed — the monotonicity is the argument. Contrast that with the coin problem, where more of a coin was not automatically better; here more reach genuinely cannot hurt.
The natural follow-up, Jump Game II, asks for the minimum number of jumps rather than mere reachability. That needs a little more — you track the current jump's frontier and the best frontier reachable within it, incrementing a counter each time you are forced to extend — but it is the same "track the frontier" idea, grown one notch.
Goal Shifting
STRATEGY
Start from the end and work backwards. The last index is our initial goal.
REACHABILITY
If an earlier index can reach the current goal, that index becomes the new goal. If goal reaches index 0, we win.