Pattern #71

0/1 Knapsack

Single-use item selection under capacity constraints. Understand why 1D DP capacity loops MUST run backwards (Right-to-Left) to prevent item reuse across Subset Sum, Target Sum, and Partition problems.

Must Solve

15 core questions β€” solve these first.

  1. 1.0/1 Knapsack
    medium
  2. 2.Partition Equal Subset Sum
    medium
  3. 3.Subset Sum
    medium
  4. 4.Target Sum
    medium
  5. 5.Last Stone Weight II
    medium
  6. 6.Minimum Subset Sum Difference
    medium
  7. 7.Count Subsets With Given Sum
    medium
  8. 8.Number of Subsets With Given Difference
    medium
  9. 9.Ones and Zeroes
    medium
  10. 10.Tallest Billboard
    hard
  11. 11.Profitable Schemes
    hard
  12. 12.Find Target Sum Ways
    medium
  13. 13.Equal Sum Partition
    medium
  14. 14.Knapsack With Exact Capacity
    medium
  15. 15.Return Selected Items in Knapsack
    medium

Also Important

5 advanced 0/1 Knapsack variations worth practicing.

  1. 16.Multi-Dimensional 0/1 Knapsack
    hard
  2. 17.Value-Based Knapsack DP
    hard
  3. 18.Bitset Subset Sum Optimization
    hard
  4. 19.Meet-in-the-Middle Subset Sum
    hard
  5. 20.Group Knapsack β€” advanced
    hard

How to Think

Each item is used ONCE. When compressing to 1D DP, iterate capacity backwards (Right -> Left).

  1. Each item usable only once?0/1 Knapsack
  2. Main decision for each item?TAKE (once) or SKIP
  3. Limited capacity / target?dp[item][capacity]
  4. Using 1D DP array?Capacity loop RIGHT -> LEFT
  5. Need split array into 2 equal parts?Target = total / 2

Go Quick Reference β€” 0/1 Knapsack

Compilable Go code demonstrating 2D matrix 0/1 Knapsack and Space-Optimized 1D Right-to-Left capacity loop.

package main

import "fmt"

// 0/1 Knapsack 2D Matrix DP: O(N * W) space
func knapsack2D(weights []int, values []int, capacity int) int {
    n := len(weights)
    dp := make([][]int, n+1)
    for i := range dp {
        dp[i] = make([]int, capacity+1)
    }

    for i := 1; i <= n; i++ {
        wt, val := weights[i-1], values[i-1]
        for w := 0; w <= capacity; w++ {
            dp[i][w] = dp[i-1][w] // SKIP item i
            if wt <= w {
                take := val + dp[i-1][w-wt] // TAKE item i (reads previous row i-1)
                if take > dp[i][w] {
                    dp[i][w] = take
                }
            }
        }
    }
    return dp[n][capacity]
}

// 0/1 Knapsack Space-Optimized 1D DP: O(W) space
func knapsack1D(weights []int, values []int, capacity int) int {
    dp := make([]int, capacity+1)
    for i := 0; i < len(weights); i++ {
        wt, val := weights[i], values[i]
        // RIGHT-TO-LEFT LOOP: prevents using current item i twice!
        for w := capacity; w >= wt; w-- {
            if val+dp[w-wt] > dp[w] {
                dp[w] = val + dp[w-wt]
            }
        }
    }
    return dp[capacity]
}

func main() {
    weights := []int{2, 3, 4}
    values := []int{6, 10, 12}
    cap := 5

    fmt.Println("2D Max Value:", knapsack2D(weights, values, cap)) // Output: 16
    fmt.Println("1D Max Value:", knapsack1D(weights, values, cap)) // Output: 16
}

πŸ‘‰ 0/1 Knapsack: O(N Γ— W) time and O(W) space with Right-to-Left loop.

Core 0/1 Mechanics & Backward Updating

Why 2D Reads Previous Row dp[i-1]

β€’ In 2D table dp[i][w] = max(dp[i-1][w], val + dp[i-1][w-wt]), taking item i reads row i-1.
β€’ Since row i-1 did not have item i available, item i can only be taken once!

Why 1D Array Loop Runs Right-to-Left

β€’ When iterating backwards (w = W down to wt), dp[w-wt] contains the UNTOUCHED value from the previous item.
β€’ Iterating forwards would overwrite dp[w-wt] with the current item, causing item reuse!

Visual Memory Rule
0/1 Definition -> Use Each Item AT MOST ONCE
2D Matrix -> dp[i][w] reads dp[i-1][w - weight] (Previous Row)
1D Array -> Loop capacity w = W down to weight (Right-to-Left / Backwards)
Partition Equal Subset Sum -> Target = total / 2 (0/1 Subset Sum)
Last Stone Weight II / Min Diff -> Target = largest S1 <= total / 2; ans = total - 2*S1
Target Sum -> Target Positive Sum P = (total + target) / 2

0/1 Knapsack Variants & Advanced Optimizations

0/1 Subset Sum Transformations

β€’ Subset Sum: Boolean dp[s] = dp[s] || dp[s - num] (Right-to-Left).
β€’ Partition Equal Subset: Check if total is even, then solve Subset Sum for target sum / 2.
β€’ Target Sum: Transform positive subset count P = (total + target) / 2.

Value-Based & Bitset Optimizations

β€’ Value-Based DP: When capacity W is huge (e.g. 10⁹) but values are small: swap state to dp[val] = min weight.
β€’ Bitset Optimization: For boolean subset sum, use bitwise shift dp |= (dp << num) for 64x speedup.

Reconstructing Selected Items

To recover chosen items from 2D DP matrix dp[N][W]:
Backtrack from i = N, w = W:
β€’ If dp[i][w] == dp[i-1][w]: Item i was SKIPPED.
β€’ Else: Item i was TAKEN -> record item i and set w -= weight[i-1].

Common Interview Mistakes

  • β€’ Iterating 1D capacity loop forwards (Left-to-Right), accidentally reusing the same item multiple times.
  • β€’ Reading current row dp[i][w - weight] in 2D take transition instead of previous row dp[i-1][w - weight].
  • β€’ Updating DP values when item weight > current capacity w (item does not fit in capacity w).
  • β€’ Using 0 base initialization for counting/boolean problems instead of dp[0] = 1 or dp[0] = true.
  • β€’ Failing to handle zeroes in Target Sum / Subset counting (zeroes double the valid subset count choices).
  • β€’ Assuming 0/1 Knapsack is polynomial in N (it is pseudo-polynomial in capacity W).
  • β€’ Attempting 1D capacity DP when W = 10^9 without checking constraint bounds (causes Memory Limit Exceeded).
  • β€’ Failing to check odd total sum in Partition Equal Subset Sum (odd sum can never be divided by 2).
  • β€’ Confusing 0/1 Knapsack (single use per item) with Unbounded Knapsack (unlimited item reuse).

Interview Rules

Single-use item selection + capacity? β†’ 0/1 Knapsack DP
Rule 1                              β†’ Define dp[i][w] state meaning explicitly
Rule 2                              β†’ 2D Matrix: dp[i][w] = max(dp[i-1][w], val + dp[i-1][w-wt])
Rule 3                              β†’ 1D Array: Loop capacity w = W down to weight (Right-to-Left)
Rule 4                              β†’ Partition Equal Subset: Target = total / 2
Rule 5                              β†’ Target Sum: Target positive subset sum P = (total + target) / 2
Rule 6                              β†’ Massive W = 10^9: Use Value-based DP or Meet-in-middle
Time Complexity                     β†’ O(N * Capacity) (Pseudo-Polynomial)
Space Complexity                    β†’ O(N * Capacity) 2D Table or O(Capacity) 1D Array

πŸ’‘ Golden Rule: Every item gets one decision onlyβ€”take it once or skip itβ€”so when you compress to 1D DP, update capacity backwards to prevent using the same item again.

Production Thinking

Budget Allocation Decisions β†’ 0/1 Knapsack models non-divisible project selections under fixed capital budgets.

Single-Use Job Scheduling β†’ 0/1 Knapsack selects maximum-value discrete jobs under server CPU capacity limits.

Storage Object Selection β†’ disk space optimization selects highest-priority discrete files under fixed byte storage caps.

Production warning β†’ capacity W scaling directly impacts table memory; for massive capacity limits (W > 10⁢), switch to Value-based DP or Meet-in-the-Middle.

Remember This

0/1 Rule: Each item usable AT MOST ONCE
1D Array: Loop Capacity RIGHT-TO-LEFT (w = W down to wt)
2D Matrix: Take reads Previous Row dp[i-1][w-wt]
Partition Equal Subset: Target = total / 2
Target Sum: Target sum P = (total + target) / 2
Pseudo-Polynomial: Time = O(N * W), Space = O(W)