Dynamic Programming · Classic DP Patterns

0/1 Knapsack

Building a 2D table where each cell (i, cap) answers "what's the best value achievable using only the first i items and a knapsack of this capacity," choosing at each item whether including it (if it fits) beats leaving it out - each item contributes to the answer at most once, which is what makes this "0/1" rather than allowing unlimited reuse.

  • 5 min read
  • 8 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The approach
  3. A second flavor: one row, swept backwards - and counting
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

You have items with weights and values and a bag with a capacity. Each item is either taken whole or left behind. Maximizing the value means choosing among 2^n subsets, and greedy shortcuts fail. Take capacity 50 and items of (weight 10, value 60), (20, 100) and (30, 120). Taking the best value per weight first gives 60 + 100 = 160, while 100 + 120 = 220 fits. A DP over the items considered so far and the capacity used solves it exactly. Many other problems, such as subset sums, target sums and partitions, are this same table in disguise.

We'll go from simple to more technical:

  1. The pattern: dp[i][cap] built from the previous row, either skip the item or take it once.
  2. A second flavor: one row swept from high to low, and counting instead of maximizing.
  3. Where knapsack shows up in production (budgets, packing, resource allocation).
  4. What breaks it: reusing items, greedy ratios, huge capacities, parity checks.

The approach

Java

int knapsack01(int capacity, int[] weights, int[] values) {
    int n = weights.length;
    int[][] dp = new int[n + 1][capacity + 1];         // dp[i][cap] = best value from the first i items
    for (int i = 1; i <= n; i++) {
        for (int cap = 0; cap <= capacity; cap++) {
            dp[i][cap] = dp[i - 1][cap];                                   // skip item i
            if (weights[i - 1] <= cap) {
                dp[i][cap] = Math.max(dp[i][cap],
                        dp[i - 1][cap - weights[i - 1]] + values[i - 1]);  // take it - built on the PREVIOUS row
            }
        }
    }
    return dp[n][capacity];
}

Python

def knapsack_01(capacity: int, weights: list[int], values: list[int]) -> int:
    n = len(weights)
    dp = [[0] * (capacity + 1) for _ in range(n + 1)]  # dp[i][cap] = best value from the first i items
    for i in range(1, n + 1):
        for cap in range(capacity + 1):
            dp[i][cap] = dp[i - 1][cap]                                 # skip item i
            if weights[i - 1] <= cap:
                dp[i][cap] = max(dp[i][cap],
                                 dp[i - 1][cap - weights[i - 1]] + values[i - 1])   # take it - built on the PREVIOUS row
    return dp[n][capacity]

Both options read row i - 1: the best achievable before item i was available. That is what makes each item usable at most once. Reading dp[i][cap - w] instead would allow item i again on top of itself - unbounded knapsack: one item of weight 1 and value 1 with capacity 3 is worth 1, but the current-row version says 3.

A second flavor: one row, swept backwards - and counting

Only the previous row is ever read, so one array is enough - if capacities are swept from high to low, so dp[cap - w] still holds the previous item's value when it's read:

Java

int[] dp = new int[capacity + 1];
for (int i = 0; i < n; i++)
    for (int cap = capacity; cap >= weights[i]; cap--)          // high to low: "previous row" preserved
        dp[cap] = Math.max(dp[cap], dp[cap - weights[i]] + values[i]);

Python

dp = [0] * (capacity + 1)
for w, v in zip(weights, values):
    for cap in range(capacity, w - 1, -1):          # high to low (the stop is exclusive): "previous row" preserved
        dp[cap] = max(dp[cap], dp[cap - w] + v)

Swept low to high, dp[cap - w] may already include item i, and the example above again returns 3. The same 0/1 table counts as well as maximizes. Target Sum - put + or - in front of each number to reach a target - becomes "count subsets summing to P" once you notice the plus-set P and minus-set N satisfy P - N = target and P + N = total, so P = (total + target) / 2 (and there are no ways at all if that's odd or |target| > total).

Where you'll see it

Budget allocation (which projects to fund under a fixed budget), cargo and container loading, choosing which ads fill a fixed slot length, and picking features for a release under an engineering-hours cap are 0/1 knapsack. Real systems use the DP when capacities are modest integers, and switch to branch-and-bound or approximation schemes when they aren't.

Which tool for which job?

You needUse
Max value, moderate capacitydp over (items, capacity), O(n × capacity)
Max value, only the valueone row swept high to low
Which items were takenthe 2D table, walked back
Huge capacity, small valuesdp over value: the minimum weight for each value
Which sums are reachablea bitset, shifted and OR-ed per item
Count the ways (Target Sum)the same table with + in place of max
Items can be splitfractional knapsack: greedy by ratio

Common mistakes

MistakeWhat happensDo this instead
Sweeping the 1D row low to highitems are reused (300 instead of 220)high to low
Greedy by value per weight160 instead of 220DP
A capacity of 10^9the table can't existindex the DP by value instead
Target Sum without the parity checkcounts the wrong subset sizeno ways if total + target is odd
The 1D version when items are neededthe choices are lostkeep the 2D table, or "taken" bits

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. Why does the take-option read dp[i - 1][cap - w] rather than dp[i][cap - w]?

Show answer

Row i - 1 is the best without item i, so adding item i once is safe. Row i may already contain item i, and building on it takes the item again - the unbounded version. With one item (weight 1, value 1) and capacity 3, the 0/1 answer is 1 and the current-row version gives 3.

2. In the 1D version, why must capacities be swept from high to low?

Show answer

dp[cap - w] must still hold the value from before item i was considered. Going downward, smaller capacities are updated after larger ones, so they're still "previous row" values when read. Going upward, dp[cap - w] was just updated with item i, allowing reuse.

3. How does Target Sum turn into a subset-counting knapsack?

Show answer

Split the numbers into the plus-set P and minus-set N. Then P - N = target and P + N = total, so P = (total + target) / 2. Count the subsets summing to that P with the 0/1 counting table (ways[s] += ways[s - x], swept downward) - after checking total + target is even and |target| <= total.

Key takeaways

  • 0/1 knapsack: for each item and capacity, skip it or take it on top of the previous row.
  • Reading the previous row (or sweeping a 1D table downward) is what keeps each item to one use.
  • Greedy by ratio is wrong for whole items; it's only right for fractional knapsack.
  • O(n * capacity) is pseudo-polynomial - fine for small capacities, hopeless for huge ones.
  • The same table counts subsets: Target Sum reduces to counting subsets that sum to (total + target) / 2.

Quick revise

The whole skill on one page, for the night before an interview.

This page puts the lesson and the deep dive together. Skim it before practice or an interview, and go back to the full lesson for anything you don't recognize.

Pick the tool

You needUse
Max valuedp over capacity, sweep down
The itemsthe 2D table, walked back
Huge capacitydp over value
Reachable sumsbitset shift-or
Count waysthe same table with +
Splittable itemsgreedy by ratio

Snippets worth knowing by heart

Java

int[] dp = new int[cap + 1];
for (int i = 0; i < n; i++)
    for (int c = cap; c >= w[i]; c--)                  // high to low: each item once
        dp[c] = Math.max(dp[c], dp[c - w[i]] + v[i]);

Python

bits = 1                                 # bit s set = sum s reachable
for x in items:
    bits |= bits << x
can_partition = total % 2 == 0 and bits >> (total // 2) & 1

The traps

  • Sweeping up: a low-to-high 1D sweep reuses items. Sweep capacity high to low.
  • Greedy by ratio: a dense item can block a better pair. Use DP.
  • Capacity in the billions: the table can't exist. Index the DP by value instead.
  • Target Sum parity: without the check you count the wrong subset size. Return 0 ways when total + target is odd.
  • 1D table when the items are needed: the choices are lost. Keep the 2D table, or record "taken" bits.

Staff-level points, one line each

  • Value-indexed DP: 961 cells instead of 5,000,000,001 for capacity 5 × 10^9. It agreed with brute force on 200 instances.
  • Walking the 2D table back recovered optimal, fitting item sets on 200 instances.
  • Bitset subset sums: at least 10x faster in Python; Java's BigInteger found the same 100,128 reachable sums.
  • Pseudo-polynomial: polynomial in the capacity's value, exponential in its bits. Meet in the middle or branch and bound beyond that.
  • Capacity 50 example: high-to-low gives 220, low-to-high 300 (an item reused).

Say it in an interview

  • "Each item is skipped or taken once, from the previous row."
  • "In 1D I sweep the capacity downwards, so an item isn't reused."
  • "If the capacity is huge but the values are small, I index by value."
  • "It's pseudo-polynomial, not polynomial."

Practice

This skill comes with 8 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice 0/1 Knapsack free

0/1 Knapsack

MediumProCoding problemJava · Python

Given n items, each with a weight and a value, and a knapsack of a given capacity, find the maximum total value achievable by choosing a subset of items (each item taken whole or not at all) whose total weight does not exceed the capacity.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Target Sum

MediumProCoding problemJava · Python

Put a + or a - sign in front of each number in nums and add everything up. Count how many of the sign choices give exactly target.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice 0/1 Knapsack free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime