494. Target Sum

Assign + or - to each number to hit a target. The trick is seeing that this is a subset-sum count: the “positive subset” must sum to (total + target) / 2, turning an exponential DFS into an O(n × goal) 1-D DP.

Medium0/1 KnapsackSubset SumDFS + MemoizationTypeScript

PROBLEM What we're solving

Given an integer array nums and an integer target, assign a + or - sign to each number and return the count of expressions that evaluate to target.

Worked example: nums = [1, 1, 1, 1, 1], target = 3. There are 5 ways: -1+1+1+1+1, +1-1+1+1+1, +1+1-1+1+1, +1+1+1-1+1, +1+1+1+1-1. Answer: 5.

KEY IDEA Split into two subsets, solve subset-sum count

Insight → Let P be the subset assigned +. Then P − (total − P) = target, so P = (total + target) / 2. The number of ways to reach target equals the number of subsets that sum to this goal— a classic 0/1 knapsack count solvable in O(n × goal) with a 1-D DP array.

RECIPE Reduce and count subsets

  • 0 · Quick reject. (total + target) must be even (otherwise no integer split exists). Also bail if goal < 0.
  • 1 · Compute goal. goal = (total + target) / 2. We need to count subsets of nums that sum to exactly goal.
  • 2 · Init DP. dp[j] = number of subsets summing to j. Set dp[0] = 1 (one way to reach 0: pick nothing).
  • 3 · Fill 0/1 knapsack. For each num iterate j from goal down to num: dp[j] += dp[j − num]. Reverse order prevents counting the same element twice.
  • 4 · Return. dp[goal] is the answer.
Classic confusion → the inner loop must go backwards (j = goal … num). Going forwards would let the same num be picked multiple times, turning the 0/1 knapsack into an unbounded one and overcounting badly.

COST Complexity & alternatives

Brute-force DFS (all ± combos)
O(2ⁿ)
Explores every assignment; n=20 already hits 1M.
1-D DP (subset count)
O(n × goal)
O(goal) space; reduces to a 1-D array sweep.

Memoized DFS with state (index, remaining)also achieves O(n × total) time and is the natural alternative if you spot the recurrence before the algebraic reduction.

Pattern transfer →this same “positive/negative subset” algebra appears in Partition Equal Subset Sum (can goal be reached at all?), Last Stone Weight II(minimize the difference → maximize a subset ≤ half of total), and any problem where you assign signs to elements and count configurations.

RUN IT Count subsets summing to (total + target) / 2

step 0 / 22
STARTnums = [1, 1, 1, 1, 1], target = 3. Compute total = 5.
1function findTargetSumWays(nums: number[], target: number): number {
2 const total = nums.reduce((a, b) => a + b, 0);
3
4 // Assign + to a subset P and - to the rest.
5 // P - (total - P) = target => P = (total + target) / 2
6 // So: count subsets of nums that sum to exactly goal.
7 if ((total + target) % 2 !== 0) return 0; // no integer split
8 const goal = (total + target) / 2;
9 if (goal < 0) return 0;
10
11 // dp[j] = number of subsets summing to j
12 const dp: number[] = new Array(goal + 1).fill(0);
13 dp[0] = 1; // one way to make sum 0: pick nothing
14
15 for (const num of nums) {
16 // iterate backwards to avoid using num twice
17 for (let j = goal; j >= num; j--) {
18 dp[j] += dp[j - num];
19 }
20 }
21
22 return dp[goal];
23}
State
5
total
3
target
goal
i
num
j
dp
current num / index icell being updated (j)source dp[j-num]non-zero count / result
slowfast

TYPESCRIPT The solution, annotated

findTargetSumWays.ts
function findTargetSumWays(nums: number[], target: number): number {
  const total = nums.reduce((a, b) => a + b, 0);

  // Assign + to a subset P and - to the rest.
  // P - (total - P) = target  =>  P = (total + target) / 2
  // So: count subsets of nums that sum to exactly goal.
  if ((total + target) % 2 !== 0) return 0;   // no integer split
  const goal = (total + target) / 2;
  if (goal < 0) return 0;

  // dp[j] = number of subsets summing to j
  const dp: number[] = new Array(goal + 1).fill(0);
  dp[0] = 1;   // one way to make sum 0: pick nothing

  for (const num of nums) {
    // iterate backwards to avoid using num twice
    for (let j = goal; j >= num; j--) {
      dp[j] += dp[j - num];
    }
  }

  return dp[goal];
}

Reading it block by block

Lines 2–9 — reduce to subset-sum goal. Sum the whole array. If (total + target) is odd there is no valid integer split, so return 0 immediately. Otherwise goal = (total + target) / 2— the exact sum the “positive” subset must reach. A negative goal also means zero ways.
Lines 12–13 — init the DP array. dp[j] counts subsets that sum to j. The empty subset sums to 0, so dp[0] = 1. Every other entry starts at 0 (no subsets yet considered).
Lines 15–19 — 0/1 knapsack fill. For each number, sweep j downward from goal to num. The update dp[j] += dp[j − num]says: “add the count of all ways to reach j − num using previous numbers, because we can extend each of those ways by picking num.” The downward direction ensures each element is used at most once.
Line 22 — return result. After processing all numbers, dp[goal] holds the total number of subsets whose sum equals goal, which equals the number of sign assignments that reach target.
Complexity → O(n × goal) time and O(goal) space, where goal = (sum(nums) + target) / 2. In the worst case goal ≤ sum(nums) ≤ 1000 (per constraints), so the DP is very fast in practice.

INTERVIEWFollow-ups they'll ask

  • “Return all the actual expressions, not just the count?” Switch to backtracking DFS and collect strings — or reconstruct them from the DP table by tracing which elements were included.
  • “What if numbers can be negative?” The reduction assumes all nums are non-negative. With negatives the goal formula still holds but goalcould exceed the array bounds — you'd need to memoize on (index, remaining_sum) instead.
  • “What is the brute-force complexity?”Pure DFS explores every sign assignment: O(2ⁿ). For n = 20 that's about 1 million calls; for n = 30 it's a billion. The DP avoids this entirely.
  • “Could you solve it with memoized DFS instead?” Yes: recurse on (index, remaining) and cache results in a Map. Same asymptotic complexity but potentially easier to derive in an interview before spotting the algebraic trick.
  • “Edge cases?” When target exceeds total (unreachable), when nums contains zeros (zeros double-count — but the DP handles them correctly since dp[j] += dp[j − 0] doubles existing ways), and when target = −total (all negatives, one way).

OPTIMAL 0/1 Knapsack

findTargetSumWays.ts
function findTargetSumWays(nums: number[], target: number): number {
  const total = nums.reduce((a, b) => a + b, 0);

  // Assign + to a subset P and - to the rest.
  // P - (total - P) = target  =>  P = (total + target) / 2
  // So: count subsets of nums that sum to exactly goal.
  if ((total + target) % 2 !== 0) return 0;   // no integer split
  const goal = (total + target) / 2;
  if (goal < 0) return 0;

  // dp[j] = number of subsets summing to j
  const dp: number[] = new Array(goal + 1).fill(0);
  dp[0] = 1;   // one way to make sum 0: pick nothing

  for (const num of nums) {
    // iterate backwards to avoid using num twice
    for (let j = goal; j >= num; j--) {
      dp[j] += dp[j - num];
    }
  }

  return dp[goal];
}
Complexity → O(n × goal) time and O(goal) space, where goal = (sum(nums) + target) / 2. In the worst case goal ≤ sum(nums) ≤ 1000 (per constraints), so the DP is very fast in practice.

ALT 1 Brute force — DFS over every ± assignment

O(2ⁿ) time · O(n) space

Try both a + and a - sign for each number, recursing to the end and counting every full assignment whose running sum equals target — the literal definition of the problem.

approach-2.ts
function findTargetSumWays(nums: number[], target: number): number {
  const n = nums.length;

  // dfs(i, sum) = number of ways to sign nums[i..] so the total hits target,
  // given the prefix nums[0..i-1] already contributed sum.
  const dfs = (i: number, sum: number): number => {
    if (i === n) return sum === target ? 1 : 0;
    // Branch: assign + to nums[i], then assign - to nums[i].
    return dfs(i + 1, sum + nums[i]) + dfs(i + 1, sum - nums[i]);
  };

  return dfs(0, 0);
}
Note → Every element doubles the number of branches, so the recursion tree has 2ⁿ leaves — n = 20 already means ~1M calls and n = 30 a billion. Memoizing on (i, sum)collapses overlapping states to O(n × sum), and the algebraic subset-sum reduction removes the recursion entirely.

MNEMONIC The one-liner

"Positive pile P minus negative pile equals target — so P is exactly (total+target)/2. Count subsets hitting that goal."

TRIGGERS When you see ___ → reach for ___

assign + or − to each element, count ways to reach targetsubset-sum DP, goal = (total+target)/2
count subsets summing to a fixed value1-D 0/1 knapsack, iterate j backwards
two complementary subsets (P and N) that differ by a fixed amountsum algebra: P = (total+delta)/2
DFS with (index, remaining) and overlapping sub-problemsmemoized DFS or flatten to DP

SKELETON The reusable shape

skeleton.ts
function findTargetSumWays(nums: number[], target: number): number {
  const total = nums.reduce((a, b) => a + b, 0);
  if ((total + target) % 2 !== 0) return 0;
  const goal = (total + target) / 2;
  if (goal < 0) return 0;

  const dp = new Array(goal + 1).fill(0);
  dp[0] = 1;
  for (const num of nums) {
    for (let j = goal; j >= num; j--) {
      dp[j] += dp[j - num];
    }
  }
  return dp[goal];
}

FLASHCARDS Tap to flip

What is the key algebraic reduction for Target Sum?
Let P = sum of “+” elements. Then P − (total − P) = target P = (total + target) / 2. Count subsets summing to P.
tap to flip
1 / 7
Answer to reveal explanations. No penalty for retries.Score: 0 / 9
QUESTION 01
For nums = [1,1,1,1,1], target = 3, what is goal = (total + target) / 2?
QUESTION 02
Why do we iterate j backwards in the knapsack inner loop?
QUESTION 03
When should findTargetSumWays immediately return 0?
QUESTION 04
What is the time complexity of the 1-D DP solution?
QUESTION 05
dp[0] is initialized to 1. What does that represent?
QUESTION 06
Which sibling problem uses the SAME algebraic subset reduction?
QUESTION 07
For nums = [1, 1, 1, 1, 1], target = 3, what does the algorithm return?
QUESTION 08
#494 · Target SumAssigning +/− signs reduces to finding a subset whose sum equals (total+target)÷2. Count such subsets via 0/1 knapsack DP, or via memoized (index, remaining) DFS.Which algorithmic approach does this primarily use?
QUESTION 09
#494 · Target SumAssigning +/− signs reduces to finding a subset whose sum equals (total+target)÷2. Count such subsets via 0/1 knapsack DP, or via memoized (index, remaining) DFS.Which implementation correctly solves it?