Pattern #73

Subset Sum

Determine if a subset of elements can form an exact target sum. Learn why empty sets make dp[0] = true, why 1D loops MUST run backwards (Right-to-Left), and how Partition Equal Subset Sum maps to Subset Sum.

Must Solve

15 core questions — solve these first.

  1. 1.Subset Sum
    medium
  2. 2.Partition Equal Subset Sum
    medium
  3. 3.Target Sum
    medium
  4. 4.Last Stone Weight II
    medium
  5. 5.Minimum Subset Sum Difference
    medium
  6. 6.Count Subsets With Given Sum
    medium
  7. 7.Number of Subsets With Given Difference
    medium
  8. 8.Equal Sum Partition
    medium
  9. 9.Split Array Into Two Equal Sum Sets
    medium
  10. 10.Ones and Zeroes — multi-capacity relation
    medium
  11. 11.Tallest Billboard — advanced relation
    hard
  12. 12.Matchsticks to Square — related partition thinking
    medium
  13. 13.Partition to K Equal Sum Subsets — advanced
    medium
  14. 14.Closest Subsequence Sum — advanced
    hard
  15. 15.Meet-in-the-Middle Subset Sum
    hard

Also Important

5 advanced Subset Sum variations worth practicing.

  1. 16.Subset Sum Using Bitset
    hard
  2. 17.Return One Valid Subset
    medium
  3. 18.Count Number of Valid Subsets
    medium
  4. 19.Minimum Number of Elements to Reach Target
    medium
  5. 20.Subset Sum With Large Target — alternative approaches
    hard

How to Think

Each number is usable ONCE. Iterate target backwards (Right -> Left).

  1. Need to choose some elements to make target?Subset Sum DP
  2. Each number usable only once?0/1 Knapsack (Right-to-Left loop)
  3. Need only possible / impossible?Boolean DP (dp[s] = dp[s] || dp[s-num])
  4. Using 1D DP array?Loop target RIGHT -> LEFT (sum = target down to num)
  5. Base Case for Target 0?dp[0] = true (empty set sum = 0)

Go Quick Reference — Subset Sum

Compilable Go code demonstrating Boolean 1D Subset Sum (Right-to-Left loop) and Partition Equal Subset Sum.

package main

import "fmt"

// Subset Sum 1D Boolean DP: O(N * Target) time, O(Target) space
func subsetSum(nums []int, target int) bool {
    dp := make([]bool, target+1)
    dp[0] = true // Base case: Empty set has sum 0

    for _, num := range nums {
        // RIGHT-TO-LEFT LOOP: prevents using num twice!
        for s := target; s >= num; s-- {
            if dp[s-num] {
                dp[s] = true
            }
        }
    }
    return dp[target]
}

// Partition Equal Subset Sum: Check even total sum + Subset Sum
func canPartition(nums []int) bool {
    totalSum := 0
    for _, num := range nums { totalSum += num }

    if totalSum%2 != 0 {
        return false // Odd total sum can never be divided equally
    }
    return subsetSum(nums, totalSum/2)
}

func main() {
    nums := []int{1, 5, 11, 5}
    fmt.Println("Subset Sum (Target 11):", subsetSum(nums, 11)) // Output: true
    fmt.Println("Can Equal Partition:", canPartition(nums))       // Output: true
}

👉 Subset Sum: O(N × Target) time and O(Target) boolean array space.

Core Subset Mechanics & Reachable Set Expansion

Why Base Case dp[0] = true

• An empty set {} always has sum 0.
• dp[0] = true acts as the seed from which all subsequent element sums expand!

Reachable Set Mental Model

• Start: {0}.
• Add 2: {0, 2}.
• Add 3: {0, 2, 3, 5}.
Updating backwards (s = target down to num) prevents new sums from expanding twice in the same step.

Visual Memory Rule
Subset Sum Base Case -> dp[0] = true (Empty Set Sum = 0)
1D Array Update -> Loop target s = target down to num (Right-to-Left)
Partition Equal Subset Sum -> Target = total / 2 (Check total%2 == 0)
Last Stone Weight II / Min Diff -> Target = largest S1 <= total / 2; ans = total - 2*S1
Target Sum -> Target Positive Sum P = (total + target) / 2
Bitset Optimization -> dp |= (dp << num) for 64x parallel speedup

Subset Sum Variants & Advanced Optimizations

Boolean vs Counting Combine Operation

• Boolean Reachability: dp[s] = dp[s] || dp[s - num].
• Counting Subsets: dp[s] = dp[s] + dp[s - num].
• Zero handling: Each zero in counting doubles the number of valid subset choices.

Bitset & Meet-in-the-Middle Optimizations

• Bitset Optimization: reachable |= (reachable << num) shifts all sums in parallel.
• Meet-in-the-Middle: When N <= 40 and target T is huge (e.g. 10⁹): split N into two N/2 halves + binary search.

Reconstructing Chosen Subset Elements

To recover chosen elements from 2D DP matrix dp[N][Target]:
Backtrack from i = N, s = Target:
• If dp[i-1][s] is true: Element i can be SKIPPED.
• Else: Element i was TAKEN -> record nums[i-1] and set s -= nums[i-1].

Common Interview Mistakes

  • • Iterating 1D target loop forwards (Left-to-Right), accidentally reusing the same number multiple times.
  • • Forgetting base case initialization dp[0] = true (empty set sum = 0), leaving all states unreachable.
  • • Failing to check odd total sum in Partition Equal Subset Sum (odd sum can never be divided by 2).
  • • Mishandling zero values in Subset Sum counting (zeroes double valid subset count choices).
  • • Assuming classic 0...target boolean DP works with negative numbers without index offset shifting.
  • • Confusing Subset Sum (each number used once) with Coin Change (coins reusable unlimited times).
  • • Attempting 1D target DP when target T = 10^9 without checking constraint bounds (causes Memory Limit Exceeded).
  • • Assuming element order matters (subsets [2,3] and [3,2] represent the exact same subset selection).
  • • Building full DP table when target > totalSum (impossible to form sum larger than all numbers combined).

Interview Rules

Choose elements to reach exact target? → Subset Sum DP
Rule 1                                 → Define dp[s] boolean state meaning explicitly
Rule 2                                 → Base Case: dp[0] = true (empty set sum = 0)
Rule 3                                 → 1D Array: Loop target s = target down to num (Right-to-Left)
Rule 4                                 → Partition Equal Subset: Target = total / 2 (Odd sum = false)
Rule 5                                 → Target Sum: Positive subset target P = (total + target) / 2
Rule 6                                 → Massive T = 10^9: Use Meet-in-the-Middle or Bitset optimization
Time Complexity                        → O(N * Target) (Pseudo-Polynomial)
Space Complexity                       → O(N * Target) 2D Table or O(Target) 1D Boolean Array

💡 Golden Rule: Track which sums are reachable, and for each number update those sums backwards so that the current number can contribute at most once.

Production Thinking

Budget Feasibility Auditing → Subset Sum verifies whether any combination of line items exactly matches an allocated budget.

Indivisible Resource Packing → Subset Sum determines if fixed memory/storage blocks can fulfill exact capacity requirements.

Financial Transaction Reconciliation → Subset Sum identifies matching transaction subsets explaining target accounting balances.

Production warning → target T scaling directly impacts array size; for massive target limits (T > 10⁶), switch to Bitset or Meet-in-the-Middle.

Remember This

Subset Sum Goal: Can any element subset sum to target?
Base Case: dp[0] = true (Empty Set Sum = 0)
1D Array: Loop Target RIGHT-TO-LEFT (s = target down to num)
Partition Equal Subset: Target = total / 2
Target Sum: Target positive subset sum P = (total + target) / 2
Bitset Acceleration: reachable |= (reachable << num)
Pseudo-Polynomial: Time = O(N * T), Space = O(T)