Subset Sum Equals Target
Given an array of non-negative integers and a target, decide whether some subset sums to exactly the target.
Intuition
This is 0/1 knapsack stripped down to a yes/no question: each number is either in the subset or out, and we only care about reachable sums. Track a boolean row where dp[s] means 'some subset sums to s'. Start with dp[0] = True (the empty subset) and let each number switch on the sums it can now reach. The one detail that matters is the sweep direction: iterating sums downward keeps each number usable at most once, because a cell is only ever read from a lower index it has not yet touched this round.
0/1 knapsack compressed to one dimension. The row index disappears because each item is processed once, but that only stays correct if the capacity loop runs downward — descending order guarantees dp[s - num] still refers to the previous item's row. Loop direction encoding "use once" versus "use unlimited" is the single most transferable fact in knapsack DP.
Approach
Define reachability, not counts
dp[s] = True if some subset of the numbers seen so far sums to exactly s. Only dp[0] starts True — the empty subset always sums to zero.
Apply one number at a time
For num, any sum s that was reachable makes s + num reachable. Written in place: dp[s] |= dp[s - num] for every s >= num.
Sweep downward to keep items unique
Looping s from target down to num means dp[s - num] still refers to the previous row — the state before num existed. An upward sweep would let the same number be reused, which solves a different (unbounded) problem.
Solution & live demo
Common pitfalls
Iterating the sum upward
for s in range(num, target + 1):
if dp[s - num]: dp[s] = Truefor s in range(target, num - 1, -1):
if dp[s - num]: dp[s] = TrueAscending order lets a value updated by this item be read again by the same item, so one element gets reused any number of times — that's the unbounded variant. Descending guarantees dp[s - num] is still from the previous round.
Forgetting the empty-subset base case
dp = [False] * (target + 1)
dp = [False] * (target + 1) dp[0] = True
Sum 0 is always achievable by taking nothing, and that True is the seed every reachable sum chains back to. Without it the whole array stays false.
Stopping the inner loop at zero
for s in range(target, -1, -1):
for s in range(target, num - 1, -1):
Below num the expression s - num is negative, which in Python wraps to the end of the list and reads an unrelated entry. Stopping at num keeps every index valid.
Edge cases
Immediately True — the empty subset sums to 0, and dp[0] is seeded True.
The inner loop never runs, so dp stays all-False except dp[0] → False.
Harmless: a zero only re-marks sums already reachable, so the answer is unchanged.