Target Sum
Medium · 2-D Dynamic Programming · Explanation and code example
Dynamic Programming (Space Optimized)
How it works
We want to count the number of ways to assign + and - signs to the numbers so that their final sum equals the target.
In the bottom-up DP approach, we used a separate data structure for each index. However, at each step, the new states depend only on the previous step, not on all earlier steps.
This means we can reuse a single data structure to keep track of all possible sums and how many ways each sum can be formed, updating it as we process each number.
Steps
- Create a map
dpwhere:- the key represents a possible sum
- the value represents the number of ways to form that sum
- Initialize
dp[0] = 1since there is exactly one way to reach sum0before using any numbers - For each number in the array:
- Create a new map
next_dpto store updated sums
- Create a new map
- For every
(total, count)indp:- Add the current number → update
next_dp[total + num] - Subtract the current number → update
next_dp[total - num]
- Add the current number → update
- Replace
dpwithnext_dpafter processing the current number - After all numbers are processed,
dp[target]gives the number of valid ways - Return
dp[target]
Complexity
- Time complexity:
O(n * m) - Space complexity:
O(m)
Where
nis the length of the arraynumsandmis the sum of all the elements in the array.
Things to watch for
Incorrect Memoization Key
When using top-down DP, the state must include both the current index and the running sum. Forgetting to include one of these in the memoization key will cause incorrect caching and wrong results. The key should be (index, total) not just index or just total.
Not Handling Negative Sums in Array-Based DP
When using an array instead of a hashmap for memoization, the running sum can become negative. You must offset the sum by the total sum of all elements to convert it to a valid non-negative index. For example, use dp[i][total + totalSum] where totalSum is the sum of all numbers.
Modifying Map While Iterating (Space-Optimized DP)
In the space-optimized bottom-up approach, you cannot update dp while iterating over it. You must create a new map next_dp for the next state and then replace dp with next_dp after processing each number. Modifying dp in place leads to using updated values in the same iteration.
Python code example
from typing import List
from collections import defaultdict
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
dp = defaultdict(int)
dp[0] = 1
for num in nums:
next_dp = defaultdict(int)
for total, count in dp.items():
next_dp[total + num] += count
next_dp[total - num] += count
dp = next_dp
return dp[target]
C++ code example
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
unordered_map<int, int> dp;
dp[0] = 1;
for (int num : nums) {
unordered_map<int, int> nextDp;
for (auto& entry : dp) {
int total = entry.first;
int count = entry.second;
nextDp[total + num] += count;
nextDp[total - num] += count;
}
dp = nextDp;
}
return dp[target];
}
};
Source and license
Adapted from NeetCode’s source · MIT license. Node types are supplied by LeetCode where needed.