🧠
Top-Down Memoization (Recursion + Caching)
💡 Memoization caches results of subproblems to avoid repeated work, drastically improving brute force efficiency.
Intuition
Use recursion but store results for each index and current sum to prevent recomputation.
Algorithm
- Calculate total sum and check if even; return false if odd.
- Use a recursive helper with memo to store results for (index, current sum).
- At each step, try including or excluding current element.
- Return true if target sum is reached; otherwise false.
💡 Memoization prunes the recursion tree by remembering solved states, reducing exponential calls.
Recurrence:f(i, sum) = f(i-1, sum) OR f(i-1, sum - nums[i-1]) with memoization
def canPartition(nums):
total = sum(nums)
if total % 2 != 0:
return False
target = total // 2
memo = {}
def dfs(i, curr_sum):
if curr_sum == target:
return True
if i == len(nums) or curr_sum > target:
return False
if (i, curr_sum) in memo:
return memo[(i, curr_sum)]
include = dfs(i + 1, curr_sum + nums[i])
exclude = dfs(i + 1, curr_sum)
memo[(i, curr_sum)] = include or exclude
return memo[(i, curr_sum)]
return dfs(0, 0)
# Example usage
if __name__ == '__main__':
print(canPartition([1,5,11,5])) # True
print(canPartition([1,2,3,5])) # False
Line Notes
memo = {}Initialize cache to store results of subproblems
if (i, curr_sum) in memo:Return cached result if subproblem already solved
memo[(i, curr_sum)] = include or excludeStore result to avoid recomputation
include = dfs(i + 1, curr_sum + nums[i])Try including current element
exclude = dfs(i + 1, curr_sum)Try excluding current element
import java.util.*;
public class Solution {
private Map<String, Boolean> memo = new HashMap<>();
public boolean canPartition(int[] nums) {
int total = 0;
for (int num : nums) total += num;
if (total % 2 != 0) return false;
int target = total / 2;
return dfs(nums, 0, 0, target);
}
private boolean dfs(int[] nums, int i, int currSum, int target) {
if (currSum == target) return true;
if (i == nums.length || currSum > target) return false;
String key = i + "," + currSum;
if (memo.containsKey(key)) return memo.get(key);
boolean include = dfs(nums, i + 1, currSum + nums[i], target);
boolean exclude = dfs(nums, i + 1, currSum, target);
memo.put(key, include || exclude);
return memo.get(key);
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.canPartition(new int[]{1,5,11,5})); // true
System.out.println(sol.canPartition(new int[]{1,2,3,5})); // false
}
}
Line Notes
private Map<String, Boolean> memo = new HashMap<>();Cache results of subproblems using string keys
String key = i + "," + currSum;Create unique key for current state
if (memo.containsKey(key)) return memo.get(key);Return cached result if available
memo.put(key, include || exclude);Store computed result
boolean include = dfs(nums, i + 1, currSum + nums[i], target);Try including current element
boolean exclude = dfs(nums, i + 1, currSum, target);Try excluding current element
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
class Solution {
unordered_map<string, bool> memo;
public:
bool canPartition(vector<int>& nums) {
int total = 0;
for (int num : nums) total += num;
if (total % 2 != 0) return false;
int target = total / 2;
return dfs(nums, 0, 0, target);
}
private:
bool dfs(vector<int>& nums, int i, int currSum, int target) {
if (currSum == target) return true;
if (i == nums.size() || currSum > target) return false;
string key = to_string(i) + "," + to_string(currSum);
if (memo.count(key)) return memo[key];
bool include = dfs(nums, i + 1, currSum + nums[i], target);
bool exclude = dfs(nums, i + 1, currSum, target);
memo[key] = include || exclude;
return memo[key];
}
};
int main() {
Solution sol;
vector<int> nums1 = {1,5,11,5};
vector<int> nums2 = {1,2,3,5};
cout << boolalpha << sol.canPartition(nums1) << endl; // true
cout << boolalpha << sol.canPartition(nums2) << endl; // false
return 0;
}
Line Notes
unordered_map<string, bool> memo;Cache subproblem results with string keys
string key = to_string(i) + "," + to_string(currSum);Create unique key for current state
if (memo.count(key)) return memo[key];Return cached result if exists
memo[key] = include || exclude;Store computed result
bool include = dfs(nums, i + 1, currSum + nums[i], target);Try including current element
bool exclude = dfs(nums, i + 1, currSum, target);Try excluding current element
function canPartition(nums) {
const total = nums.reduce((a, b) => a + b, 0);
if (total % 2 !== 0) return false;
const target = total / 2;
const memo = new Map();
function dfs(i, currSum) {
if (currSum === target) return true;
if (i === nums.length || currSum > target) return false;
const key = i + ',' + currSum;
if (memo.has(key)) return memo.get(key);
const include = dfs(i + 1, currSum + nums[i]);
const exclude = dfs(i + 1, currSum);
memo.set(key, include || exclude);
return memo.get(key);
}
return dfs(0, 0);
}
// Example usage
console.log(canPartition([1,5,11,5])); // true
console.log(canPartition([1,2,3,5])); // false
Line Notes
const memo = new Map();Cache results of subproblems
const key = i + ',' + currSum;Create unique key for memoization
if (memo.has(key)) return memo.get(key);Return cached result if available
memo.set(key, include || exclude);Store computed result
const include = dfs(i + 1, currSum + nums[i]);Try including current element
const exclude = dfs(i + 1, currSum);Try excluding current element
TimeO(n * target)
SpaceO(n * target) for memo
Memoization reduces exponential calls to polynomial by caching states defined by index and sum.
💡 For n=20 and target=1000, this is about 20,000 operations, which is efficient.
Interview Verdict: Accepted - efficient for given constraints
Memoization is a practical optimization that makes the problem solvable within time limits.