Subset sum using DP
1. Subset Sum (Decision Problem)
Pattern: DP
Idea:
π» Code
def subsetSum(nums, i, target, dp):
if target == 0:
return True
if i == len(nums):
return False
if dp[i][target] != -1:
return dp[i][target]
ans = subsetSum(nums, i + 1, target, dp)
if not ans and target >= nums[i]:
ans = subsetSum(nums, i + 1,
target - nums[i], dp)
dp[i][target] = ans
return ans
Initialization
dp = [[-1]*(target+1) for _ in range(len(nums))]
Time complexity - O(n * S) Aux. Space complexity - O(n * S) Basic Recursion - 6. Subset Sum Problem, Also below. Variations:-
Problem
Given an array and a target sum S, determine whether at least one subset has sum equal to S.
Example
nums = [2,3,7,8,10]
target = 11
Output:
True
Subset:
3 + 8 = 11
Recursive State
Define
f(i, sum)
Meaning:
Can we form
sumusing elements from indexionward?
Choices
For every element,
either
Take it
or
Skip it
Recurrence
f(i,sum) =
f(i+1,sum)
OR
f(i+1,sum-nums[i])
Base Cases
if sum == 0:
return True
if i == len(nums):
return False
Recursive Code
def subsetSum(nums, i, target):
if target == 0:
return True
if i == len(nums):
return False
return (
subsetSum(nums, i + 1, target)
or
(target >= nums[i] and
subsetSum(nums, i + 1, target - nums[i]))
)
Memoization
State:
dp[i][sum]
def subsetSum(nums, i, target, dp):
if target == 0:
return True
if i == len(nums):
return False
if dp[i][target] != -1:
return dp[i][target]
ans = subsetSum(nums, i + 1, target, dp)
if not ans and target >= nums[i]:
ans = subsetSum(nums, i + 1,
target - nums[i], dp)
dp[i][target] = ans
return ans
Initialization
dp = [[-1]*(target+1) for _ in range(len(nums))]
Tabulation
State
dp[i][s]
Can first i elements make sum s?
Transition
dp[i][s] =
dp[i-1][s]
or
dp[i-1][s-nums[i-1]]
Code
def subsetSum(nums, target):
n = len(nums)
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 >= nums[i-1]:
dp[i][s] |= dp[i-1][s-nums[i-1]]
return dp[n][target]
Space Optimized
Observation:
Each row depends only on the previous row.
Code
def subsetSum(nums, target):
dp = [False]*(target+1)
dp[0] = True
for x in nums:
for s in range(target,
x-1,
-1):
dp[s] |= dp[s-x]
return dp[target]
Why iterate backwards?
To ensure every element is used only once.
Forward iteration would reuse the same element multiple times.
Complexity
| Approach | Time | Aux Space |
|---|---|---|
| Recursion | O(2βΏ) | O(n) |
| Memoization | O(n Γ S) | O(n Γ S) |
| Tabulation | O(n Γ S) | O(n Γ S) |
| Space Optimized | O(n Γ S) | O(S) |
Pattern Recognition
Whenever you see
Generate all subsets
β
Think
Backtracking
Bitmasking
Whenever you see
Subset exists?
Count subsets?
Equal partition?
Target sum?
Minimum difference?
β
Think
Subset Sum DP
Interview Connections
Subset Sum
β
βββ Count Subsets
β
βββ Equal Partition
β
βββ Target Sum
β
βββ Perfect Sum
β
βββ 0/1 Knapsack
FAANG Takeaways
β Learn the state before memorizing the code.
dp[i][sum]
or
dp[sum]
is the heart of every variation.
β Remember the transition:
Decision Problem
OR
β
Count Problem
+
β Equal Partition is just Subset Sum after reducing the target to
totalSum // 2
β For 1-D DP, always iterate the sums backwards.
This guarantees that each array element is used at most once, which is exactly the requirement of the 0/1 Subset Sum family.