🧠
Top-Down DP with Memoization
💡 Memoization avoids recomputing the same states by caching results, drastically improving efficiency over brute force.
Intuition
Store results of recursive calls keyed by index and current sum to reuse when the same state is encountered again.
Algorithm
- Use a recursive function with parameters index and current sum.
- Check if the state (index, current sum) is in memo; if yes, return cached result.
- If index equals length of nums, return 1 if current sum equals target else 0.
- Recursively compute ways by adding and subtracting current number, store in memo, and return.
💡 Memoization prunes the recursion tree by avoiding repeated work, turning exponential time into polynomial.
Recurrence:f(i, sum) = f(i+1, sum + nums[i]) + f(i+1, sum - nums[i])
def findTargetSumWays(nums, target):
memo = {}
def backtrack(i, current_sum):
if i == len(nums):
return 1 if current_sum == target else 0
if (i, current_sum) in memo:
return memo[(i, current_sum)]
memo[(i, current_sum)] = backtrack(i+1, current_sum + nums[i]) + backtrack(i+1, current_sum - nums[i])
return memo[(i, current_sum)]
return backtrack(0, 0)
# Example usage
if __name__ == '__main__':
print(findTargetSumWays([1,1,1,1,1], 3)) # Output: 5
Line Notes
memo = {}Initialize cache to store computed states
if (i, current_sum) in memo:Check if result already computed to avoid recomputation
memo[(i, current_sum)] = ...Store computed result for current state
return memo[(i, current_sum)]Return cached or newly computed result
import java.util.HashMap;
import java.util.Map;
public class Solution {
private Map<String, Integer> memo = new HashMap<>();
public int findTargetSumWays(int[] nums, int target) {
return backtrack(nums, target, 0, 0);
}
private int backtrack(int[] nums, int target, int index, int currentSum) {
if (index == nums.length) {
return currentSum == target ? 1 : 0;
}
String key = index + "," + currentSum;
if (memo.containsKey(key)) {
return memo.get(key);
}
int count = backtrack(nums, target, index + 1, currentSum + nums[index]) +
backtrack(nums, target, index + 1, currentSum - nums[index]);
memo.put(key, count);
return count;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.findTargetSumWays(new int[]{1,1,1,1,1}, 3)); // 5
}
}
Line Notes
private Map<String, Integer> memo = new HashMap<>();Cache to store results keyed by index and sum
String key = index + "," + currentSum;Create unique key for memoization
if (memo.containsKey(key))Return cached result if available
memo.put(key, count);Store computed count for current state
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
class Solution {
unordered_map<string, int> memo;
public:
int findTargetSumWays(vector<int>& nums, int target) {
return backtrack(nums, target, 0, 0);
}
private:
int backtrack(vector<int>& nums, int target, int index, int currentSum) {
if (index == nums.size()) {
return currentSum == target ? 1 : 0;
}
string key = to_string(index) + "," + to_string(currentSum);
if (memo.count(key)) return memo[key];
memo[key] = backtrack(nums, target, index + 1, currentSum + nums[index]) +
backtrack(nums, target, index + 1, currentSum - nums[index]);
return memo[key];
}
};
int main() {
Solution sol;
vector<int> nums = {1,1,1,1,1};
cout << sol.findTargetSumWays(nums, 3) << endl; // 5
return 0;
}
Line Notes
unordered_map<string, int> memo;Cache to store computed states
string key = to_string(index) + "," + to_string(currentSum);Unique key for memoization
if (memo.count(key)) return memo[key];Return cached result if present
memo[key] = ...Store computed count for current state
function findTargetSumWays(nums, target) {
const memo = new Map();
function backtrack(i, currentSum) {
if (i === nums.length) {
return currentSum === target ? 1 : 0;
}
const key = i + ',' + currentSum;
if (memo.has(key)) return memo.get(key);
const count = backtrack(i + 1, currentSum + nums[i]) + backtrack(i + 1, currentSum - nums[i]);
memo.set(key, count);
return count;
}
return backtrack(0, 0);
}
// Example usage
console.log(findTargetSumWays([1,1,1,1,1], 3)); // 5
Line Notes
const memo = new Map();Initialize cache for memoization
const key = i + ',' + currentSum;Create unique key for current state
if (memo.has(key)) return memo.get(key);Return cached result if available
memo.set(key, count);Store computed count for reuse
TimeO(n * sum(nums))
SpaceO(n * sum(nums)) for memo
Memoization prunes recursion to unique (index, sum) states bounded by n and sum of nums.
💡 For n=20 and sum=1000, this reduces calls from millions to about 20,000 states.
Interview Verdict: Accepted
Memoization is efficient enough for given constraints and is a common interview solution.