🧠
Memoization (Top-Down DP)
💡 Memoization avoids recomputing the same subproblems by caching results, drastically improving efficiency over brute force.
Intuition
Store results of recursive calls keyed by current index and current sum to reuse them instead of recomputing.
Algorithm
- Create a memo dictionary keyed by (index, current_sum).
- Before recursive calls, check if result is cached; if yes, return it.
- Otherwise, compute counts including and excluding current element.
- Store and return the sum of counts.
💡 Memoization requires careful key design and understanding that overlapping subproblems exist.
def count_subsets_memo(arr, K, index=0, current_sum=0, memo=None):
if memo is None:
memo = {}
if index == len(arr):
return 1 if current_sum == K else 0
if (index, current_sum) in memo:
return memo[(index, current_sum)]
count_incl = count_subsets_memo(arr, K, index + 1, current_sum + arr[index], memo)
count_excl = count_subsets_memo(arr, K, index + 1, current_sum, memo)
memo[(index, current_sum)] = count_incl + count_excl
return memo[(index, current_sum)]
# Driver code
if __name__ == '__main__':
arr = [1, 2, 3, 3]
K = 6
print(count_subsets_memo(arr, K))
Line Notes
memo = {}Initialize memo dictionary to cache results
if (index, current_sum) in memo:Check if subproblem already solved
memo[(index, current_sum)] = count_incl + count_exclStore computed result to avoid recomputation
return memo[(index, current_sum)]Return cached or newly computed result
import java.util.HashMap;
import java.util.Map;
public class CountSubsetsMemo {
public static int countSubsets(int[] arr, int K, int index, int currentSum, Map<String, Integer> memo) {
if (index == arr.length) {
return currentSum == K ? 1 : 0;
}
String key = index + "," + currentSum;
if (memo.containsKey(key)) {
return memo.get(key);
}
int countIncl = countSubsets(arr, K, index + 1, currentSum + arr[index], memo);
int countExcl = countSubsets(arr, K, index + 1, currentSum, memo);
memo.put(key, countIncl + countExcl);
return memo.get(key);
}
public static void main(String[] args) {
int[] arr = {1, 2, 3, 3};
int K = 6;
Map<String, Integer> memo = new HashMap<>();
System.out.println(countSubsets(arr, K, 0, 0, memo));
}
}
Line Notes
public static int countSubsets(int[] arr, int K, int index, int currentSum, Map<String, Integer> memo)Recursive method with memo map passed as parameter to cache results
String key = index + "," + currentSum;Create unique key for current subproblem
if (memo.containsKey(key))Return cached result if available
memo.put(key, countIncl + countExcl);Store computed result in memo
#include <iostream>
#include <vector>
#include <unordered_map>
#include <string>
using namespace std;
unordered_map<string, int> memo;
int countSubsets(const vector<int>& arr, int K, int index = 0, int currentSum = 0) {
if (index == arr.size()) {
return currentSum == K ? 1 : 0;
}
string key = to_string(index) + "," + to_string(currentSum);
if (memo.find(key) != memo.end()) {
return memo[key];
}
int countIncl = countSubsets(arr, K, index + 1, currentSum + arr[index]);
int countExcl = countSubsets(arr, K, index + 1, currentSum);
memo[key] = countIncl + countExcl;
return memo[key];
}
int main() {
vector<int> arr = {1, 2, 3, 3};
int K = 6;
cout << countSubsets(arr, K) << endl;
return 0;
}
Line Notes
unordered_map<string, int> memo;Global memo map to store subproblem results
string key = to_string(index) + "," + to_string(currentSum);Create unique string key for memoization
if (memo.find(key) != memo.end())Return cached result if found
memo[key] = countIncl + countExcl;Cache computed result for reuse
const memo = new Map();
function countSubsets(arr, K, index = 0, currentSum = 0) {
if (index === arr.length) {
return currentSum === K ? 1 : 0;
}
const key = index + ',' + currentSum;
if (memo.has(key)) {
return memo.get(key);
}
const countIncl = countSubsets(arr, K, index + 1, currentSum + arr[index]);
const countExcl = countSubsets(arr, K, index + 1, currentSum);
memo.set(key, countIncl + countExcl);
return memo.get(key);
}
// Test
const arr = [1, 2, 3, 3];
const K = 6;
console.log(countSubsets(arr, K));
Line Notes
const memo = new Map();Map to cache results of subproblems
const key = index + ',' + currentSum;Unique key for current subproblem
if (memo.has(key))Return cached result if available
memo.set(key, countIncl + countExcl);Store computed count in memo
TimeO(n * K)
SpaceO(n * K) for memo storage and recursion stack
Memoization avoids recomputation by caching results for each index and sum combination, reducing exponential calls to polynomial.
💡 For n=20 and K=100, this means at most 2000 subproblems, which is much faster than brute force.
Interview Verdict: Accepted for moderate constraints
Memoization makes the solution efficient enough for moderate input sizes and is a common interview optimization.