AlgoMaster Logo

Introduction to the 0/1 Knapsack Pattern

High Priority11 min readUpdated May 30, 2026
Listen to this chapter
Unlock Audio

The 0/1 Knapsack problem captures a fundamental trade-off: given a set of items each with a weight and a value, and a container with a fixed capacity, choose which items to include so that the total value is maximized without exceeding the capacity. The "0/1" means each item is either taken (1) or skipped (0). No fractions, no duplicates.

Many problems reduce to knapsack. Subset sum, partition problems, target sum, and some string problems all share the same core decision: pick or skip items to hit a capacity constraint. Learning the knapsack framework makes an entire class of DP problems approachable.

What Is the 0/1 Knapsack Pattern?

You have n items, where item i has weight w[i] and value v[i], and a knapsack with capacity W. Find the maximum total value you can carry without exceeding W. Each item can be used at most once.

The "0/1" distinction matters. The unbounded variant allows taking an item multiple times; the fractional variant allows fractions (solved by greedy). The 0/1 version is the hardest because taking the highest value-per-weight item first does not guarantee an optimal solution. You need to consider all combinations, which is where DP comes in.

The decision tree for a small example with 3 items and capacity 5:

The tree has 2^n leaves, but different paths can arrive at the same state (same remaining capacity, same items left). This overlapping substructure is what makes DP applicable.

How to Identify 0/1 Knapsack Problems

Not every DP problem is a knapsack problem, but many are. The signals:

  1. Subset selection: You are choosing a subset from a collection of items
  2. Capacity constraint: There is a limit (weight, sum, count, budget) you cannot exceed
  3. Each item used at most once: No repetition allowed
  4. Optimization or feasibility: Either maximize/minimize something, or determine if a target is achievable

When these signals appear together, think knapsack.

Problem TypeItemsCapacityValue
Classic 0/1 KnapsackPhysical itemsWeight limitItem values
Subset SumNumbers in arrayTarget sumFeasibility (true/false)
Partition Equal Subset SumNumbers in arraytotalSum / 2Feasibility (true/false)
Target Sum (+/- signs)Numbers in array(totalSum + target) / 2Count of ways
Coin Change (limited coins)Coin denominationsTarget amountMin coins
Last Stone Weight IIStone weightstotalSum / 2Min remaining weight

The items, capacity, and "value" change between problems, but the core decision, take or skip each item against a capacity, stays the same.

The Core Idea

State Definition

Define dp[i][w] as the maximum value achievable using the first i items with a knapsack capacity of w.

  • i ranges from 0 to n (number of items)
  • w ranges from 0 to W (knapsack capacity)

Transition

For each item i, we have two choices:

  1. Skip item i: The best value is whatever we got from the first i-1 items with the same capacity: dp[i-1][w]
  2. Take item i (only if w[i] <= w): The value is v[i] plus the best value from the first i-1 items with reduced capacity: v[i] + dp[i-1][w - w[i]]

We pick whichever is larger:

Base Cases

  • dp[0][w] = 0 for all w: with zero items, the value is zero regardless of capacity
  • dp[i][0] = 0 for all i: with zero capacity, we cannot take anything

Filling the Table

Fill row by row, left to right. Each cell depends on two cells from the previous row: one directly above (skip) and one shifted left (take).

This dependency pattern enables the space optimization.

Space Optimization: 2D to 1D

Since each row only depends on the previous row, we do not need the entire 2D table. A single 1D array of size W+1 is enough. There is a catch though.

If we iterate capacity from left to right (0 to W), then when we compute dp[w], the value at dp[w - w[i]] has already been updated in the current iteration. That means we would be using item i's contribution twice, effectively solving the unbounded knapsack instead.

Iterate capacity from right to left (W down to w[i]). When we read dp[w - w[i]], it still holds the value from the previous row (before item i was considered).

Right-to-left iteration in the 1D array ensures each item is used at most once.

Example Walkthrough: Partition Equal Subset Sum (LeetCode 416)

Problem Statement

Given an integer array nums, return true if you can partition the array into two subsets such that the sum of elements in both subsets is equal.

Example: nums = [1, 5, 11, 5] returns true because [1, 5, 5] and [11] both sum to 11.

Constraints: 1 <= nums.length <= 200, 1 <= nums[i] <= 100

Intuition

At first glance, this looks like a partitioning problem, not a knapsack problem. Reframe it.

If the total sum of all numbers is S, then we need two subsets that each sum to S/2. If S is odd, there is no valid partition, so we return false immediately.

The problem becomes: can we find a subset of nums that sums to exactly S/2?

This is a subset sum problem, which is a special case of 0/1 knapsack:

  • Items: the numbers in the array
  • Capacity: S/2
  • Goal: determine if we can fill the capacity exactly (feasibility, not optimization)

Instead of tracking maximum value, our DP tracks reachability: dp[w] is true if some subset of the numbers seen so far sums to exactly w.

The transition becomes:

If we could already reach sum w (skip the current number), or if we could reach w - nums[i] (take the current number to bridge the gap), then sum w is achievable.

Step-by-Step Trace

Trace through nums = [1, 5, 11, 5].

Total sum = 22, target = 11.

We use a 1D boolean array dp[0..11], initialized with dp[0] = true (empty subset sums to 0).

We can stop early. dp[11] is true, so the partition exists. The third number (11) by itself forms one subset, and [1, 5, 5] forms the other.

Iterating right-to-left prevented us from using the same number twice. When we processed nums[0] = 1, we did not set dp[2] = true (which would have meant using the number 1 twice).

Implementation

The space-optimized 1D solution:

Complexity Analysis

  • Time Complexity: O(n * target), where n is the length of nums and target is totalSum / 2. For each of the n numbers, we iterate through up to target capacities.
  • Space Complexity: O(target) for the 1D DP array. The 2D approach would use O(n * target), but the right-to-left iteration trick reduces this to a single row.

A note on pseudo-polynomial complexity

O(n * target) looks polynomial, but target is a value, not the size of the input. The number of bits needed to represent target is log(target), so the runtime is `O(n 2^log(target)), exponential in the input length.

This is called pseudo-polynomial: fast when values are small (target up to 10^4-10^6), infeasible when values are large (target = 10^18). 0/1 Knapsack and Subset Sum are both NP-complete in the strong sense of weight magnitude; the DP works only because the typical interview constraints keep target` small. A common follow-up: "What if the weights can be up to 10^9?"

The Knapsack Family: Same Template, Different Aggregator

Partition Equal Subset Sum is one of a family of problems that all reduce to 0/1 knapsack with small variations. Recognizing the family is the difference between solving a new problem in five minutes and re-deriving the recurrence under interview pressure. The shared template is always:

dp[i][w] = combine( dp[i-1][w], dp[i-1][w - weight[i]] (with item i applied) )

What changes between problems is what dp[i][w] stores and how combine aggregates the two options. Once that mapping is clear, the code is identical up to the aggregator.

The Family

Problemdp[i][w] StoresAggregatorItem Effect
0/1 Knapsack (max value)Max value achievable with capacity w using first i itemsmaxdp[i-1][w - weight[i]] + value[i]
Subset Sum (LC 416 base)Boolean: is sum w achievable using first i items?ORdp[i-1][w - nums[i]]
Partition Equal Subset Sum (LC 416)Same as Subset Sum, with target = totalSum / 2ORdp[i-1][w - nums[i]]
Target Sum (LC 494)Number of ways to reach sum w using first i itemssumdp[i-1][w - nums[i]]
Last Stone Weight II (LC 1049)Same as Subset Sum, with target = totalSum / 2ORdp[i-1][w - stones[i]]
Ones and Zeroes (LC 474)Max subset size fitting in (w0 zeros, w1 ones) using first i itemsmaxdp[i-1][w0 - z[i]][w1 - o[i]] + 1
Count of Subset SumsNumber of subsets summing to w using first i itemssumdp[i-1][w - nums[i]]

How To Recognize Each One In An Interview

  • Asks for maximum/minimum value with a weight or capacity bound → 0/1 Knapsack, aggregator max or min.
  • Asks whether some target is achievable → Subset Sum, aggregator OR.
  • Asks how many ways to reach some target → Count variant, aggregator sum.
  • Asks to assign +/- signs and reach a target → Target Sum. Reduce to Subset Sum (see below).
  • Asks to partition into two subsets and optimize their difference → Last Stone Weight II / Partition Subset Sum. Reduce by computing min |S1 - S2| where S1 + S2 = totalSum and S1 = sum of a subset. The minimum is achieved when S1 is as close to totalSum / 2 as possible.
  • Two simultaneous capacity dimensions → 2D-capacity knapsack (Ones and Zeroes). Same template, one extra DP axis.

The Target Sum Reduction

LC 494 (Target Sum) asks: given an array nums and an integer target, how many ways are there to assign + or - to each number so the sum equals target?

Let P be the subset of numbers assigned +, and N be the subset assigned -. Then:

  • P - N = target
  • P + N = totalSum

Adding the two: 2P = totalSum + target, so P = (totalSum + target) / 2.

The problem reduces to: how many subsets of nums sum to (totalSum + target) / 2? This is a count-variant subset sum, aggregator sum.

Edge cases for the reduction:

  • If (totalSum + target) is odd, no valid assignment exists, return 0.
  • If |target| > totalSum, no valid assignment exists, return 0.

Recovering The Chosen Items

A frequent follow-up: "Don't just return the maximum value. Return which items achieve it."

Build the DP table as usual. Then walk it backward from dp[n][W]:

The condition dp[i][w] != dp[i-1][w] means including item i changed the answer; therefore item i was used. This recovery walk is O(n) and only works on the 2D table; if you space-optimized to 1D, you lose the ability to reconstruct the chosen items.

In interviews, mention the space-time trade-off: O(W) space or item reconstruction, not both.

Quiz

Introduction Quiz

10 quizzes