🧠
Top-Down Memoization (Recursion + Cache)
💡 Memoization avoids recomputing results for the same positions, improving efficiency while keeping the recursive structure.
Intuition
Store results of subproblems (positions) to avoid repeated work when exploring jumps recursively.
Algorithm
- Create a cache to store if a position can reach the end.
- Start recursion from index 0.
- If position is cached, return cached result.
- Try all jumps from current position; cache and return true if any succeed.
- If none succeed, cache and return false.
💡 Memoization prunes the recursion tree by remembering results, making the algorithm much faster.
def canJump(nums):
n = len(nums)
memo = [None] * n
def backtrack(position):
if position == n - 1:
return True
if memo[position] is not None:
return memo[position]
furthest_jump = min(position + nums[position], n - 1)
for next_pos in range(position + 1, furthest_jump + 1):
if backtrack(next_pos):
memo[position] = True
return True
memo[position] = False
return False
return backtrack(0)
# Example usage
if __name__ == '__main__':
print(canJump([2,3,1,1,4])) # True
print(canJump([3,2,1,0,4])) # False
Line Notes
memo = [None] * nInitialize cache to store results for each position
if memo[position] is not None:Return cached result if already computed
memo[position] = TrueCache success when a path from this position reaches end
memo[position] = FalseCache failure when no path from this position reaches end
return backtrack(0)Start recursion from index 0
import java.util.Arrays;
public class Solution {
private Boolean[] memo;
public boolean canJump(int[] nums) {
memo = new Boolean[nums.length];
return backtrack(nums, 0);
}
private boolean backtrack(int[] nums, int position) {
if (position == nums.length - 1) return true;
if (memo[position] != null) return memo[position];
int furthestJump = Math.min(position + nums[position], nums.length - 1);
for (int nextPos = position + 1; nextPos <= furthestJump; nextPos++) {
if (backtrack(nums, nextPos)) {
memo[position] = true;
return true;
}
}
memo[position] = false;
return false;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.canJump(new int[]{2,3,1,1,4})); // true
System.out.println(sol.canJump(new int[]{3,2,1,0,4})); // false
}
}
Line Notes
private Boolean[] memo;Cache to store results for each position
if (memo[position] != null) return memo[position];Return cached result if available
memo[position] = true;Cache success when path found
memo[position] = false;Cache failure when no path found
return backtrack(nums, 0);Start recursion from index 0
#include <iostream>
#include <vector>
using namespace std;
class Solution {
vector<int> memo; // -1 unknown, 0 false, 1 true
public:
bool backtrack(const vector<int>& nums, int position) {
if (position == (int)nums.size() - 1) return true;
if (memo[position] != -1) return memo[position] == 1;
int furthestJump = min(position + nums[position], (int)nums.size() - 1);
for (int nextPos = position + 1; nextPos <= furthestJump; ++nextPos) {
if (backtrack(nums, nextPos)) {
memo[position] = 1;
return true;
}
}
memo[position] = 0;
return false;
}
bool canJump(vector<int>& nums) {
memo.assign(nums.size(), -1);
return backtrack(nums, 0);
}
};
int main() {
Solution sol;
vector<int> test1 = {2,3,1,1,4};
vector<int> test2 = {3,2,1,0,4};
cout << boolalpha << sol.canJump(test1) << endl; // true
cout << boolalpha << sol.canJump(test2) << endl; // false
return 0;
}
Line Notes
vector<int> memo;Cache vector to store results: -1 unknown, 0 false, 1 true
if (memo[position] != -1) return memo[position] == 1;Return cached result if known
memo[position] = 1;Cache success when path found
memo[position] = 0;Cache failure when no path found
memo.assign(nums.size(), -1);Initialize cache with unknown state
function canJump(nums) {
const n = nums.length;
const memo = new Array(n).fill(null);
function backtrack(position) {
if (position === n - 1) return true;
if (memo[position] !== null) return memo[position];
let furthestJump = Math.min(position + nums[position], n - 1);
for (let nextPos = position + 1; nextPos <= furthestJump; nextPos++) {
if (backtrack(nextPos)) {
memo[position] = true;
return true;
}
}
memo[position] = false;
return false;
}
return backtrack(0);
}
// Example usage
console.log(canJump([2,3,1,1,4])); // true
console.log(canJump([3,2,1,0,4])); // false
Line Notes
const memo = new Array(n).fill(null);Initialize cache array with null to mark unknown states
if (memo[position] !== null) return memo[position];Return cached result if available
memo[position] = true;Cache success when path found
memo[position] = false;Cache failure when no path found
return backtrack(0);Start recursion from index 0
TimeO(n^2) in worst case
SpaceO(n) for recursion stack and memo
Memoization prunes repeated calls but still may explore many jumps per position.
💡 For n=1000, this reduces calls drastically compared to brute force but can still be slow.
Interview Verdict: Accepted but not optimal
This approach is a good improvement but can time out on very large inputs.