Subset Sum asks a yes/no question that looks simple but hides exponential blow-up: out of all possible subsets, does any one of them add up to a given target? The trick is to stop enumerating subsets and instead fill a small boolean table that remembers which sums are reachable.
Problem. Given an array of positive integers a and an integer target, return true if some
subset of a adds up to exactly target, and false otherwise. Each element may be used at most once.
Example: a = [3, 34, 4, 12], target = 7 → true, because the subset {3, 4} sums to 7.
The slow way first
The brute-force idea is to try every subset. With n items there are 2^n subsets, so checking them all is O(2ⁿ) — for 30 items that is already a billion combinations. We need to avoid re-deriving the same partial sums over and over.
The question to ask: while I decide about one item, what do I wish I already knew? I wish I knew, for every sum s, whether the earlier items could already make s. If I knew that, deciding about the next item is just a quick lookup. That is exactly what a DP table stores.
The idea: a table of reachable sums
Define dp[i][s] = "can some subset of the first i items add up to exactly s?" For each cell there are two choices for item i:
- Skip it — then the answer is whatever the row above says:
dp[i - 1][s]. - Take it — only possible if
s >= a[i - 1]; then we need the rest to makes - a[i - 1], i.e.dp[i - 1][s - a[i - 1]].
So dp[i][s] = dp[i - 1][s] OR dp[i - 1][s - a[i - 1]]. Each cell looks straight up (skip) or up-and-left by the item's value (take).
The base case seeds everything: dp[i][0] is always True (the empty subset makes sum 0), and dp[0][s] for s > 0 is False (no items, no positive sum).
Walk through it
Step through the animation. We fill the grid row by row for a = [3, 34, 4, 12] and target = 7. Watch the highlighted cells in the row above feed each new cell. Rows for 34 and 12 just copy down (both exceed the target, so they never fit). The magic happens in the +4 row: dp[3][7] reads dp[2][3] (the subset {3}) and lights up — proving {3, 4} reaches 7.
Pseudocode
make a table dp[0..n][0..target] of False
for every row i:
dp[i][0] = True # empty subset makes 0
for i from 1 to n:
for s from 1 to target:
dp[i][s] = dp[i-1][s] # skip item i
if s >= a[i-1] and dp[i-1][s - a[i-1]]:
dp[i][s] = True # take item i
return dp[n][target]The Python solution
def subset_sum(a, target):
n = len(a)
dp = [[False] * (target + 1) for _ in range(n + 1)]
for i in range(n + 1):
dp[i][0] = True
for i in range(1, n + 1):
for s in range(1, target + 1):
dp[i][s] = dp[i - 1][s]
if s >= a[i - 1]:
if dp[i - 1][s - a[i - 1]]:
dp[i][s] = True
return dp[n][target]dpis an(n + 1) x (target + 1)grid of booleans, all starting False.- The first loop sets
dp[i][0] = Truefor every row — the empty subset always reaches sum 0. - For each item
iand sums, line 8 first copies the skip case from the row above. - Line 9 guards the take case: we can only use the item if it fits (
s >= a[i - 1]). - If the leftover sum
s - a[i - 1]was reachable without this item, taking it reachess, so the cell becomes True. - The answer is the bottom-right corner,
dp[n][target].
Complexity
| Case | Time | Notes |
|---|---|---|
| Brute force (all subsets) | O(2ⁿ) (moderate) | enumerate every subset |
| DP table (this solution) | O(n · target) (moderate) | fill each cell once |
O(n · target) (moderate)This is pseudo-polynomial: fast when target is modest, but it scales with the numeric value of the target, not just the count of items. The space can be squeezed to O(target) by keeping only one row and iterating s downward — the classic rolling-array trick.
When this pattern shows up
Subset Sum is the parent of a whole family: Partition Equal Subset Sum, Target Sum, Coin Change, and
0/1 Knapsack are all "fill a boolean or numeric table over reachable totals." Spot the signature — a set
of numbers plus a target total, asking "can we hit it / how many ways / best value" — and reach for a DP
over (items, sum).
This DP relies on the values being non-negative integers so sums index into a finite table. With negative numbers or fractional values the sum axis is no longer a clean array of indices, and you need a different approach (e.g. shifting the range or using a hash set of reachable sums).
Practice
For a = [3, 34, 4, 12] and target = 7, which cell in the row above does dp[3][7] read to become True, and what subset does that represent?
1. What does dp[i][s] represent?
2. Why is dp[i][0] always True?
3. When can item i contribute to dp[i][s]?
4. What is the time complexity of the DP table?