Skip to content

494. Target Sum (Medium)

Problem

Given an integer array nums and an integer target, assign + or to each number and return the number of sign assignments whose signed sum equals target.

Example

  • nums = [1, 1, 1, 1, 1], target = 35
  • nums = [1], target = 11

LeetCode 494 · Link · Medium

Approach 1: Recursive, try both signs per element

def find_target_sum_ways(nums, target):
def f(i, cur):
if i == len(nums): # L1: O(1) base case
return 1 if cur == target else 0
return f(i + 1, cur + nums[i]) + f(i + 1, cur - nums[i]) # L2: two recursive calls
return f(0, 0)

Where the time goes, line by line

Variables: n = len(nums).

LinePer-call costTimes executedContribution
L1 (base check)O(1)once per leafO(1) each
L2 (two recursive calls)O(1) work + 2 callsevery non-leaf callO(2^n) ← dominates

Every element branches into two sign choices. The call tree is a complete binary tree of depth n with 2^n leaves.

Complexity

  • Time: O(2^n), driven by L2 double-branching at every step.
  • Space: O(n) recursion depth.

Approach 2: Top-down memoized by (i, cur)

from functools import lru_cache
def find_target_sum_ways(nums, target):
@lru_cache(maxsize=None) # L1: cache decorator
def f(i, cur):
if i == len(nums): # L2: O(1) base case
return 1 if cur == target else 0
return f(i + 1, cur + nums[i]) + f(i + 1, cur - nums[i]) # L3: O(1) with cache
return f(0, 0)

Where the time goes, line by line

Variables: n = len(nums), S = sum(nums).

LinePer-call costTimes executedContribution
L1 (lru_cache)O(1)1O(1)
L2 (base check)O(1)once per unique (i, cur)O(n · S) total
L3 (cached calls)O(1) per callat most n · (2S+1) unique statesO(n · S) ← dominates

The unique states are (i, cur) where i ranges over n+1 values and cur ranges over [-S, S]. Each state is computed once.

Complexity

  • Time: O(n · total_sum), driven by L3 across all unique (i, cur) states.
  • Space: O(n · total_sum) for the memo table.

Approach 3: Subset-sum transformation + 1-D DP (canonical)

Let P = positive-signed subset, N = negative-signed. Then P + N = total and P - N = targetP = (total + target) / 2. So: count subsets that sum to P. That’s classic 0/1 subset-sum.

Edge cases: total + target must be even and non-negative.

def find_target_sum_ways(nums, target):
total = sum(nums) # L1: O(n)
if (total + target) % 2 or total < abs(target): # L2: O(1) feasibility check
return 0
P = (total + target) // 2 # L3: O(1) target subset sum
dp = [0] * (P + 1) # L4: O(P) table init
dp[0] = 1 # L5: O(1) empty subset base case
for x in nums: # L6: O(n) outer loop over elements
for s in range(P, x - 1, -1): # L7: O(P) inner loop, right-to-left
dp[s] += dp[s - x] # L8: O(1) accumulate ways
return dp[P] # L9: O(1) answer

Where the time goes, line by line

Variables: n = len(nums), P = (sum(nums) + target) / 2.

LinePer-call costTimes executedContribution
L1-L5 (init)O(n) + O(P)1O(n + P)
L6+L7 (double loop)O(1) bodyn · PO(n · P) ← dominates
L8 (DP update)O(1)n · Pincluded above

The right-to-left inner loop (L7) is critical for 0/1 knapsack correctness: it ensures each element is used at most once. If you iterated left-to-right, the same x could be counted multiple times in a single pass.

Complexity

  • Time: O(n · P), driven by L6/L7 (the double loop).
  • Space: O(P) for the 1-D DP array.

Summary

ApproachTimeSpace
Naive sign-enumerationO(2^n)O(n)
Top-down memoO(n · total_sum)O(n · total_sum)
Subset-sum + 1-D DPO(n · P)O(P)

The “convert to subset sum” transformation is a classic move, same technique solves “Last Stone Weight II” (1049).

Test cases

# Quick smoke tests, paste into a REPL or save as test_494.py and run.
# Uses the canonical implementation (Approach 3: subset-sum + 1-D DP).
def find_target_sum_ways(nums, target):
total = sum(nums)
if (total + target) % 2 or total < abs(target):
return 0
P = (total + target) // 2
dp = [0] * (P + 1)
dp[0] = 1
for x in nums:
for s in range(P, x - 1, -1):
dp[s] += dp[s - x]
return dp[P]
def _run_tests():
# problem statement examples
assert find_target_sum_ways([1, 1, 1, 1, 1], 3) == 5
assert find_target_sum_ways([1], 1) == 1
# edge: target unreachable (parity)
assert find_target_sum_ways([1, 1], 0) == 2
# edge: target exceeds total sum
assert find_target_sum_ways([1, 2], 4) == 0
# single element, negative target
assert find_target_sum_ways([1], -1) == 1
# all zeros
assert find_target_sum_ways([0, 0, 0], 0) == 8
print("all tests pass")
if __name__ == "__main__":
_run_tests()
  • Arrays, DP indexed by running subset sum