Target Sum
Table of Contents + β
Target Sum looks like a math puzzle. You get some numbers and you must put a plus or a minus in front of each one. The interviewer wants to see one thing. Can you count all the ways to reach a target, without trying every combination by brute force? That counting trick is the real test here.
π― The Problem
You get some numbers and one target. Here are the rules.
- The array holds non-negative numbers.
- You put either a
+or a-sign in front of every number. - Then you add them all up.
- You return the count of sign choices that reach the target.
- You do not return the signs themselves. Only the count.
For [1, 1, 1, 1, 1] with target 3, one valid way is -1 + 1 + 1 + 1 + 1 = 3. There are more ways too.
Input: nums = [1, 1, 1, 1, 1], target = 3Output: 5
Explanation: These five sign choices each reach 3: -1 +1 +1 +1 +1 = 3 +1 -1 +1 +1 +1 = 3 +1 +1 -1 +1 +1 = 3 +1 +1 +1 -1 +1 = 3 +1 +1 +1 +1 -1 = 3Here is a picture of the choices. At each number you branch into a plus path and a minus path. Every leaf is one full sign choice.
π’ Approach 1: Try Every Sign (Brute Force)
The idea in one line: try every plus and minus choice and count the ones that hit the target.
The idea:
- Go number by number.
- At each number, branch into two paths. One adds it. One subtracts it.
- At the end of the array, check the running sum.
How it works:
- This is plain recursion. A function that calls itself on a smaller piece.
- The smaller piece is βthe rest of the array after this numberβ.
- If the running sum equals the target at the end, count one.
Why it is weak:
- Every number doubles the number of paths.
- With
nnumbers you get about2to the powernpaths. - Twenty numbers already means over a million paths. Too slow.
Here is the try-every-sign code:
def find_target_sum_ways(nums, target): def dfs(index, total): if index == len(nums): return 1 if total == target else 0 return dfs(index + 1, total + nums[index]) + dfs(index + 1, total - nums[index])
return dfs(0, 0)β‘ Approach 2: Add Memory With Memoization (Better)
The idea in one line: many paths reach the same spot, so save each spotβs answer once.
The idea:
- A βspotβ is a current index plus a current running sum.
- Many different paths land on the same
(index, sum)spot. - The answer from that spot never changes.
How it works:
- Memoization saves the answer the first time we reach a spot.
- Next time we reach it, we read the saved answer.
- We store the answer for each
(index, sum)pair. - The sum can be negative, so some languages shift it to stay non-negative.
Why it is faster:
- Each
(index, sum)spot is solved once, not over and over. - The work drops to about the number of distinct spots.
Here is the memoized recursion:
from functools import lru_cache
def find_target_sum_ways(nums, target): @lru_cache(None) def dfs(index, total): if index == len(nums): return 1 if total == target else 0 return dfs(index + 1, total + nums[index]) + dfs(index + 1, total - nums[index])
return dfs(0, 0)π Approach 3: Turn It Into Subset Sum (Best)
The idea in one line: split the numbers into a plus group and a minus group, then count subsets.
Here is the clever insight. Split the numbers into two groups. One group P gets a plus sign. The other group N gets a minus sign. Then:
sum(P) - sum(N) = targetAdd sum(P) + sum(N) (which is the total of all numbers) to both sides:
2 * sum(P) = target + totalsum(P) = (target + total) / 2So the question becomes simple.
How it works:
- Count how many subsets add up to
(target + total) / 2. - A subset is some of the numbers chosen out of the array.
- Build a table
dpwheredp[s]is the number of subsets that sum tos. - Start with
dp[0] = 1. There is one way to make sum zero: pick nothing. - For each number, update the table from high sums down to low sums.
- Going downward keeps each number used only once.
Why it is fast:
- One table, filled once per number.
- If
(target + total)is odd, ortargetis bigger thantotal, the answer is0. No split can work.
Steps to Solve
- Add up all the numbers to get
total. - If
target + totalis odd, return0. Iftargetis larger thantotal, return0. - Compute
s = (target + total) / 2. This is the subset sum we need to count. - Make an array
dpof sizes + 1, all zeros, and setdp[0] = 1. - For each number, loop the sum index from
sdown to the number. Adddp[index - number]intodp[index]. - The answer is
dp[s].
This Python version is the cleanest. It turns the problem into subset-sum counting and fills one list.
def find_target_sum_ways(nums, target): total = sum(nums)
# an impossible target or odd split has zero ways if abs(target) > total or (target + total) % 2 != 0: return 0
s = (target + total) // 2 # subset sum we must count dp = [0] * (s + 1) dp[0] = 1 # one way to make sum 0: pick nothing
for num in nums: for j in range(s, num - 1, -1): # go downward to use each number once dp[j] += dp[j - num]
return dp[s]
nums = [1, 1, 1, 1, 1]target = 3print(find_target_sum_ways(nums, target))The output of the above code will be:
5Let us walk through the Python version line by line. This is where the idea clicks.
total = sum(nums)We add every number up first. We need the total to do the subset-sum math.
if abs(target) > total or (target + total) % 2 != 0: return 0If the target is bigger than the total we can never reach it. And if target + total is odd then (target + total) / 2 is not a whole number. So no subset sum exists. Both cases mean zero ways.
s = (target + total) // 2This is the magic line. We proved that the count of valid sign choices equals the count of subsets that add up to s. So now we just count subsets.
dp = [0] * (s + 1)dp[0] = 1Here dp[j] will hold the number of subsets that add up to j. We start with dp[0] = 1. There is exactly one way to make a sum of zero. Pick no numbers at all.
for num in nums: for j in range(s, num - 1, -1): dp[j] += dp[j - num]For each number we update the table. We loop j from s down to num. Going downward matters a lot. It makes sure each number is added to a subset only once. If we went upward we would use the same number many times by mistake. The line dp[j] += dp[j - num] says: every subset that made j - num can now include this number to make j.
return dp[s]At the end dp[s] holds the count of subsets that reach s. That is our answer.
Below is a picture of the dp table filling up for the subset sum s = 4 on [1, 1, 1, 1, 1]. Each number rolls the counts forward.
β±οΈ Time and Space Complexity
The recursion tries every sign, so it is O(2^n) time. Memoization stores each (index, sum) once, so it drops to O(n * sum) time. The subset-sum table is the same order but uses just one row, so its space is small. Here s is the subset sum, which is at most the total of all numbers.
| Approach | Time Complexity | Space Complexity |
|---|---|---|
| Recursion (try every sign) | O(2^n) | O(n) |
| Memoization over (index, sum) | O(n * sum) | O(n * sum) |
| Subset-sum tabulation (1D) | O(n * s) | O(s) |
Tip
In an interview, start with the plus and minus recursion. Then show how the same spots repeat, which leads to memoization. Then reveal the subset-sum transform. Walking that whole path shows deep understanding, not just a memorized answer.
π§© Key Takeaways
- β A plus and minus choice problem can become a subset-sum counting problem.
- β
The needed subset sum is
(target + total) / 2. If that is not a whole number, the answer is0. - β
Set
dp[0] = 1, because there is one way to make sum zero: pick nothing. - β Loop the sum index downward so each number is counted only once.
- β
The final answer sits in
dp[s], the count of subsets reaching the needed sum.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
What does the Target Sum problem ask you to return?
Why: You return how many ways of placing plus and minus signs add up to the target, not the signs themselves.
- 2
What subset sum do we count after the transform?
Why: Splitting into a plus group and minus group gives sum(P) = (target + total) / 2, so we count subsets reaching that value.
- 3
Why do we loop the sum index downward when filling the dp table?
Why: Going downward stops a number from being added into the same subset more than once.
- 4
When is the answer immediately zero?
Why: An odd (target + total) means the needed subset sum is not a whole number, and a target larger than total can never be reached.