You're given an array of non-negative integers. You start standing on the first index. The number at each index is the maximum jump length you can take from there — from index i you may step forward anywhere from 1 to nums[i] indices (a 0 means you can't move at all). Picture a row of stepping stones across a river, where each stone is marked with how far you're allowed to leap from it. Return true if there's some sequence of jumps that lands you on (or past) the last index, and false if you'd always get stranded. This is the classic Jump Game problem.
// nums: non-negative integers; nums[i] is the MAX jump length from index i
// returns: true if the last index is reachable from index 0, else false
function arrayReachableEnd(nums: number[]): boolean;
arrayReachableEnd([2, 3, 1, 1, 4]);
// → true
// From index 0 jump 1 to index 1 (value 3), then jump 3 to index 4. Reached.
arrayReachableEnd([3, 2, 1, 0, 4]);
// → false
// Every route lands on index 3, whose value is 0. You're stuck before the end.
nums[i] is a maximum, not an exact distance. From a stone marked 3 you may jump 1, 2, or 3 forward — whatever helps.true. You're already standing on the last index.0 is only fatal if you have no way to jump over or past it from an earlier stone.You'll decide whether a sequence of jumps can carry you from the first index to the last, using each value as the most you're allowed to leap from that spot.
You're crossing a river on a row of stepping stones. Each stone has a number painted on it: the most stones you're allowed to leap forward from there. From a stone marked 3 you may land on the next stone, the one after, or the third one over — your choice. A stone marked 0 is a dead end: once you're standing on it, you can't move. Starting on the first stone, is there any way to reach the last one? You return true or false — not the path itself, just whether the crossing is possible.
The key realization: you never need to track which path you took. All that matters at any moment is the farthest index you could possibly be standing on by now. Call it farthest. As you scan left to right, every stone you can actually reach might push that frontier further: standing on index i lets you reach up to i + nums[i]. The end is reachable exactly when the frontier ever stretches to the last index. You're stranded exactly when your scan walks onto an index the frontier hasn't covered yet.
The single operation that does the work: at each index i, update farthest = max(farthest, i + nums[i]). That's the whole engine. The two questions wrapped around it — "have I already fallen off the frontier?" and "has the frontier reached the end?" — give you the answer.
The instinct is to try every jump from every stone and see if any sequence lands on the end. That's a clean recursion: from index i, try jumping 1, 2, … up to nums[i], and recurse from each landing spot. If any of them reaches the end, you win.
function canReachNaive(nums, i = 0) {
if (i >= nums.length - 1) return true; // reached or passed the last index
// try every jump length from 1 to nums[i]
for (let jump = 1; jump <= nums[i]; jump++) {
if (canReachNaive(nums, i + jump)) return true;
}
return false; // no jump from here panned out
}
This returns the right answer, but it re-solves the same subproblem over and over. Index 5 might be reachable from index 2, index 3, and index 4 — and each of those recomputes "can I reach the end from index 5?" from scratch. On an array like [2, 2, 2, 2, …] the branching compounds and the running time balloons toward exponential. You can bolt on memoization (cache the answer per index) to make it O(n²) — but that's still doing far more work than the problem needs, and it carries a recursion-stack cost too.
The fix is to stop enumerating paths and instead carry one number forward: the farthest index reachable so far. Scan once, left to right.
function arrayReachableEnd(nums) {
const last = nums.length - 1;
let farthest = 0; // furthest index we could be standing on by now
for (let i = 0; i <= last; i++) {
// if our scan has walked past everything reachable, we're stranded
if (i > farthest) return false;
// standing on i lets us reach up to i + nums[i]; extend the frontier
farthest = Math.max(farthest, i + nums[i]);
// frontier now covers (or overshoots) the last index — crossing is possible
if (farthest >= last) return true;
}
return true; // single-element array: index 0 is already the last index
}
module.exports = { arrayReachableEnd };
The shift from the naive version is the move from "explore every path" to "remember one frontier." Because a stone marked k lets you reach any index up to i + k (not just i + k exactly), every index up to the frontier is genuinely reachable — so a single max is all the bookkeeping the problem needs. One pass, one variable, O(n) time and O(1) space.
Trace arrayReachableEnd([2, 3, 1, 1, 4]). Here last = 4 and farthest starts at 0.
i = 0 — 0 > 0? No, we're still on the frontier. Update farthest = max(0, 0 + 2) = 2. Is 2 >= 4? No. Keep going.i = 1 — 1 > 2? No. Update farthest = max(2, 1 + 3) = 4. Is 4 >= 4? Yes → return true. We never even visit indices 2, 3, 4 — the frontier already reached the end.Now trace a failing case, arrayReachableEnd([3, 2, 1, 0, 4]), last = 4:
i = 0 — 0 > 0? No. farthest = max(0, 0 + 3) = 3. 3 >= 4? No.i = 1 — 1 > 3? No. farthest = max(3, 1 + 2) = 3. 3 >= 4? No.i = 2 — 2 > 3? No. farthest = max(3, 2 + 1) = 3. 3 >= 4? No.i = 3 — 3 > 3? No. farthest = max(3, 3 + 0) = 3. The zero adds nothing. 3 >= 4? No.i = 4 — 4 > 3? Yes → return false. The scan stepped onto an index the frontier never covered. We're stranded one stone short.The i > farthest guard is the heart of the failure case. It's the moment the loop counter outruns everything you could have jumped to.
nums[i] is a max, not an exact jump. From a stone marked 3 you may jump 1, 2, or 3. That's why one farthest number suffices: every index up to i + nums[i] is reachable, not only the landing at i + nums[i]. If you treat it as an exact distance you'll wrongly reject reachable arrays.i > farthest, not i >= farthest. When i === farthest you're standing on the frontier — a valid stone you reached. Only when i strictly exceeds it have you fallen into the gap. Off-by-one here flips correct answers to wrong ones.0 only strands you if no earlier stone's reach clears it. [2, 0, 1] is true because index 0 jumps straight over the zero; [1, 0, 1] is false because the only reach lands you exactly on the zero with nowhere to go.true. With nums = [0], last = 0, and you're already standing on it. The loop's first iteration sees farthest = 0 >= 0 and returns true; the return true after the loop is the same safety net for that case.[4, 0, 0] is true — farthest becomes 4, which is >= last even though a literal jump would sail past the array. The check is farthest >= last, not === last.i <= last is fine because the early returns fire first. By the time i could reference nums[last], either the frontier already reached the end (true) or the scan fell off (false). You never read past the array.O(n).Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.
You're given an array of non-negative integers. You start standing on the first index. The number at each index is the maximum jump length you can take from there — from index i you may step forward anywhere from 1 to nums[i] indices (a 0 means you can't move at all). Picture a row of stepping stones across a river, where each stone is marked with how far you're allowed to leap from it. Return true if there's some sequence of jumps that lands you on (or past) the last index, and false if you'd always get stranded. This is the classic Jump Game problem.
// nums: non-negative integers; nums[i] is the MAX jump length from index i
// returns: true if the last index is reachable from index 0, else false
function arrayReachableEnd(nums: number[]): boolean;
arrayReachableEnd([2, 3, 1, 1, 4]);
// → true
// From index 0 jump 1 to index 1 (value 3), then jump 3 to index 4. Reached.
arrayReachableEnd([3, 2, 1, 0, 4]);
// → false
// Every route lands on index 3, whose value is 0. You're stuck before the end.
nums[i] is a maximum, not an exact distance. From a stone marked 3 you may jump 1, 2, or 3 forward — whatever helps.true. You're already standing on the last index.0 is only fatal if you have no way to jump over or past it from an earlier stone.You'll decide whether a sequence of jumps can carry you from the first index to the last, using each value as the most you're allowed to leap from that spot.
You're crossing a river on a row of stepping stones. Each stone has a number painted on it: the most stones you're allowed to leap forward from there. From a stone marked 3 you may land on the next stone, the one after, or the third one over — your choice. A stone marked 0 is a dead end: once you're standing on it, you can't move. Starting on the first stone, is there any way to reach the last one? You return true or false — not the path itself, just whether the crossing is possible.
The key realization: you never need to track which path you took. All that matters at any moment is the farthest index you could possibly be standing on by now. Call it farthest. As you scan left to right, every stone you can actually reach might push that frontier further: standing on index i lets you reach up to i + nums[i]. The end is reachable exactly when the frontier ever stretches to the last index. You're stranded exactly when your scan walks onto an index the frontier hasn't covered yet.
The single operation that does the work: at each index i, update farthest = max(farthest, i + nums[i]). That's the whole engine. The two questions wrapped around it — "have I already fallen off the frontier?" and "has the frontier reached the end?" — give you the answer.
The instinct is to try every jump from every stone and see if any sequence lands on the end. That's a clean recursion: from index i, try jumping 1, 2, … up to nums[i], and recurse from each landing spot. If any of them reaches the end, you win.
function canReachNaive(nums, i = 0) {
if (i >= nums.length - 1) return true; // reached or passed the last index
// try every jump length from 1 to nums[i]
for (let jump = 1; jump <= nums[i]; jump++) {
if (canReachNaive(nums, i + jump)) return true;
}
return false; // no jump from here panned out
}
This returns the right answer, but it re-solves the same subproblem over and over. Index 5 might be reachable from index 2, index 3, and index 4 — and each of those recomputes "can I reach the end from index 5?" from scratch. On an array like [2, 2, 2, 2, …] the branching compounds and the running time balloons toward exponential. You can bolt on memoization (cache the answer per index) to make it O(n²) — but that's still doing far more work than the problem needs, and it carries a recursion-stack cost too.
The fix is to stop enumerating paths and instead carry one number forward: the farthest index reachable so far. Scan once, left to right.
function arrayReachableEnd(nums) {
const last = nums.length - 1;
let farthest = 0; // furthest index we could be standing on by now
for (let i = 0; i <= last; i++) {
// if our scan has walked past everything reachable, we're stranded
if (i > farthest) return false;
// standing on i lets us reach up to i + nums[i]; extend the frontier
farthest = Math.max(farthest, i + nums[i]);
// frontier now covers (or overshoots) the last index — crossing is possible
if (farthest >= last) return true;
}
return true; // single-element array: index 0 is already the last index
}
module.exports = { arrayReachableEnd };
The shift from the naive version is the move from "explore every path" to "remember one frontier." Because a stone marked k lets you reach any index up to i + k (not just i + k exactly), every index up to the frontier is genuinely reachable — so a single max is all the bookkeeping the problem needs. One pass, one variable, O(n) time and O(1) space.
Trace arrayReachableEnd([2, 3, 1, 1, 4]). Here last = 4 and farthest starts at 0.
i = 0 — 0 > 0? No, we're still on the frontier. Update farthest = max(0, 0 + 2) = 2. Is 2 >= 4? No. Keep going.i = 1 — 1 > 2? No. Update farthest = max(2, 1 + 3) = 4. Is 4 >= 4? Yes → return true. We never even visit indices 2, 3, 4 — the frontier already reached the end.Now trace a failing case, arrayReachableEnd([3, 2, 1, 0, 4]), last = 4:
i = 0 — 0 > 0? No. farthest = max(0, 0 + 3) = 3. 3 >= 4? No.i = 1 — 1 > 3? No. farthest = max(3, 1 + 2) = 3. 3 >= 4? No.i = 2 — 2 > 3? No. farthest = max(3, 2 + 1) = 3. 3 >= 4? No.i = 3 — 3 > 3? No. farthest = max(3, 3 + 0) = 3. The zero adds nothing. 3 >= 4? No.i = 4 — 4 > 3? Yes → return false. The scan stepped onto an index the frontier never covered. We're stranded one stone short.The i > farthest guard is the heart of the failure case. It's the moment the loop counter outruns everything you could have jumped to.
nums[i] is a max, not an exact jump. From a stone marked 3 you may jump 1, 2, or 3. That's why one farthest number suffices: every index up to i + nums[i] is reachable, not only the landing at i + nums[i]. If you treat it as an exact distance you'll wrongly reject reachable arrays.i > farthest, not i >= farthest. When i === farthest you're standing on the frontier — a valid stone you reached. Only when i strictly exceeds it have you fallen into the gap. Off-by-one here flips correct answers to wrong ones.0 only strands you if no earlier stone's reach clears it. [2, 0, 1] is true because index 0 jumps straight over the zero; [1, 0, 1] is false because the only reach lands you exactly on the zero with nowhere to go.true. With nums = [0], last = 0, and you're already standing on it. The loop's first iteration sees farthest = 0 >= 0 and returns true; the return true after the loop is the same safety net for that case.[4, 0, 0] is true — farthest becomes 4, which is >= last even though a literal jump would sail past the array. The check is farthest >= last, not === last.i <= last is fine because the early returns fire first. By the time i could reference nums[last], either the frontier already reached the end (true) or the scan fell off (false). You never read past the array.O(n).Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.