🧠
Top-Down DP with Memoization
💡 Memoization avoids recomputing states by caching results, drastically improving efficiency over brute force.
Intuition
Use recursion but store results of subproblems to avoid repeated work.
Algorithm
- Initialize a memo array to store reachability results.
- Start recursion from index 0.
- If result for current index is cached, return it.
- Try all jumps from current index and cache the result.
💡 Memoization prunes the recursion tree by remembering results, making the algorithm much faster.
Recurrence:dp[i] = OR over j in [i+1, i+nums[i]] of dp[j]
def canJump(nums):
n = len(nums)
memo = [None] * n
def dfs(pos):
if pos >= n - 1:
return True
if memo[pos] is not None:
return memo[pos]
furthest_jump = min(pos + nums[pos], n - 1)
for next_pos in range(pos + 1, furthest_jump + 1):
if dfs(next_pos):
memo[pos] = True
return True
memo[pos] = False
return False
# Driver code
if __name__ == '__main__':
print(canJump([2,3,1,1,4])) # True
print(canJump([3,2,1,0,4])) # False
Line Notes
memo = [None] * nInitialize memo array to cache results
if memo[pos] is not None:Return cached result if available
memo[pos] = TrueCache success result for current position
memo[pos] = FalseCache failure result if no jump leads to success
public class JumpGame {
public static boolean canJump(int[] nums) {
Boolean[] memo = new Boolean[nums.length];
return dfs(nums, 0, memo);
}
private static boolean dfs(int[] nums, int pos, Boolean[] memo) {
if (pos >= nums.length - 1) return true;
if (memo[pos] != null) return memo[pos];
int furthestJump = Math.min(pos + nums[pos], nums.length - 1);
for (int nextPos = pos + 1; nextPos <= furthestJump; nextPos++) {
if (dfs(nums, nextPos, memo)) {
memo[pos] = true;
return true;
}
}
memo[pos] = false;
return false;
}
public static void main(String[] args) {
System.out.println(canJump(new int[]{2,3,1,1,4})); // true
System.out.println(canJump(new int[]{3,2,1,0,4})); // false
}
}
Line Notes
Boolean[] memo = new Boolean[nums.length];Memo array to store results
if (memo[pos] != null) return memo[pos];Return cached result if exists
memo[pos] = true;Cache success for current position
memo[pos] = false;Cache failure if no path found
#include <iostream>
#include <vector>
using namespace std;
bool dfs(const vector<int>& nums, int pos, vector<int>& memo) {
if (pos >= (int)nums.size() - 1) return true;
if (memo[pos] != -1) return memo[pos];
int furthestJump = min(pos + nums[pos], (int)nums.size() - 1);
for (int nextPos = pos + 1; nextPos <= furthestJump; ++nextPos) {
if (dfs(nums, nextPos, memo)) {
memo[pos] = 1;
return true;
}
}
memo[pos] = 0;
return false;
}
int main() {
vector<int> nums1 = {2,3,1,1,4};
vector<int> nums2 = {3,2,1,0,4};
vector<int> memo1(nums1.size(), -1);
vector<int> memo2(nums2.size(), -1);
cout << boolalpha << dfs(nums1, 0, memo1) << endl; // true
cout << boolalpha << dfs(nums2, 0, memo2) << endl; // false
return 0;
}
Line Notes
vector<int>& memoMemo array passed by reference to cache results
if (memo[pos] != -1) return memo[pos];Return cached result if computed
memo[pos] = 1;Cache success for current position
memo[pos] = 0;Cache failure if no path found
function canJump(nums) {
const n = nums.length;
const memo = new Array(n).fill(null);
function dfs(pos) {
if (pos >= n - 1) return true;
if (memo[pos] !== null) return memo[pos];
const furthestJump = Math.min(pos + nums[pos], n - 1);
for (let nextPos = pos + 1; nextPos <= furthestJump; nextPos++) {
if (dfs(nextPos)) {
memo[pos] = true;
return true;
}
}
memo[pos] = false;
return false;
}
return dfs(0);
}
// Test cases
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 memo array to cache results
if (memo[pos] !== null) return memo[pos];Return cached result if available
memo[pos] = true;Cache success for current position
memo[pos] = false;Cache failure if no path leads to success
TimeO(n^2) worst case
SpaceO(n) for memo and recursion stack
Memoization avoids repeated calls but still may check up to n jumps per position.
💡 For n=1000, this means up to 1 million operations, which is acceptable.
Interview Verdict: Accepted
This approach is efficient enough for medium constraints and demonstrates DP principles.