Pattern #72

Unbounded Knapsack

Unlimited item supply resource optimization. Learn why 1D DP capacity loops MUST run forwards (Left-to-Right) to allow item reuse across Coin Change, Rod Cutting, and Perfect Squares.

Must Solve

15 core questions — solve these first.

  1. 1.Unbounded Knapsack
    medium
  2. 2.Coin Change
    medium
  3. 3.Coin Change II
    medium
  4. 4.Rod Cutting
    medium
  5. 5.Combination Sum IV
    medium
  6. 6.Perfect Squares
    medium
  7. 7.Minimum Cost to Fill Given Weight
    medium
  8. 8.Integer Break
    medium
  9. 9.Minimum Coins to Make Amount
    medium
  10. 10.Count Ways to Make Amount
    medium
  11. 11.Maximum Value With Unlimited Items
    medium
  12. 12.Cutting Rod for Maximum Profit
    medium
  13. 13.Number of Ways to Reach Target With Reusable Values
    medium
  14. 14.Complete Knapsack
    medium
  15. 15.Unlimited Supply Knapsack
    medium

Also Important

5 advanced Unbounded Knapsack variations worth practicing.

  1. 16.Coin Change With Limited/Unlimited Comparison
    medium
  2. 17.Minimum Cost With Reusable Choices
    medium
  3. 18.Count Ordered Combinations
    medium
  4. 19.Knapsack With Exact Fill
    medium
  5. 20.Multi-Dimensional Unbounded Knapsack — advanced
    hard

How to Think

Items can be reused UNLIMITED TIMES. Iterate capacity forwards (Left -> Right).

  1. Can I reuse the same item unlimited times?Unbounded Knapsack
  2. Unlimited coins or rod pieces?Unbounded Knapsack
  3. Using 1D DP array?Capacity loop LEFT -> RIGHT (Forwards)
  4. Why forward loop?dp[w] reads NEWLY updated dp[w-wt] of current item
  5. TAKE recursive transition?solve(i, cap - wt) (index stays on SAME item i)

Go Quick Reference — Unbounded Knapsack

Compilable Go code demonstrating 2D matrix Unbounded Knapsack (reads same row dp[i]) and Space-Optimized 1D Left-to-Right capacity loop.

package main

import "fmt"

// Unbounded Knapsack 2D Matrix DP: Reads SAME ROW dp[i] on TAKE
func unboundedKnapsack2D(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][w-wt] // TAKE item i (reads SAME ROW i!)
                if take > dp[i][w] {
                    dp[i][w] = take
                }
            }
        }
    }
    return dp[n][capacity]
}

// Unbounded Knapsack Space-Optimized 1D DP: O(W) space
func unboundedKnapsack1D(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]
        // LEFT-TO-RIGHT LOOP: enables reusing current item i multiple times!
        for w := wt; w <= capacity; 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{5, 8, 11}
    cap := 6

    fmt.Println("2D Max Value:", unboundedKnapsack2D(weights, values, cap)) // Output: 16 (Items 3+3)
    fmt.Println("1D Max Value:", unboundedKnapsack1D(weights, values, cap)) // Output: 16
}

👉 Unbounded Knapsack: O(N × W) time and O(W) space with Left-to-Right loop.

Core Unbounded Mechanics & Golden Difference

0/1 vs Unbounded Golden Difference

• 0/1 TAKE: solve(i+1, cap - wt) moves index to i+1 (single use).
• Unbounded TAKE: solve(i, cap - wt) STAYS on index i (unlimited reuse)!

Why 2D Reads Same Row dp[i]

• 2D matrix transition: dp[i][w] = max(dp[i-1][w], val + dp[i][w-wt]).
• Taking item i reads same row dp[i], allowing item i to be chosen again.

Visual Memory Rule
Unbounded Definition -> Item CAN BE REUSED UNLIMITED TIMES
Recursive TAKE -> solve(i, cap - wt) (Stay on SAME item i)
2D Matrix -> dp[i][w] = max(dp[i-1][w], val + dp[i][w-wt]) (Reads SAME ROW i)
1D Array -> Loop capacity w = wt up to W (Left-to-Right / Forwards)
Rod Cutting -> Capacity = Rod Length, Item Weight = Piece Length, Item Value = Price
Coin Change II -> Outer coin loop (Combinations Count)
Combination Sum IV -> Outer amount loop (Permutations Count)

Unbounded Variants & Exact Fill Initializations

Rod Cutting & Perfect Squares Mappings

• Rod Cutting: Unbounded max profit DP where piece lengths can be reused.
• Perfect Squares: Unbounded min items count DP: dp[x] = min(dp[x], 1 + dp[x - sq]).

Exact Target Fill Initializations

• Minimum Items (Coin Change): Set dp[0] = 0, all other amounts to INF.
• Combinations Count (Coin Change II): Set dp[0] = 1, all other amounts to 0.

Reconstructing Used Choice Sequences

To recover chosen coins/items in 1D Unbounded DP:
Maintain an array choice[w] recording which item index i improved dp[w].
Backtrack from w = target: record choice[w], then set w -= weight[choice[w]] until w == 0.

Common Interview Mistakes

  • • Iterating 1D capacity loop backwards (Right-to-Left), accidentally enforcing 0/1 single item use.
  • • Reading previous row dp[i-1][w-wt] in 2D TAKE transition instead of same row dp[i][w-wt].
  • • Advancing item index i -> i+1 in recursive TAKE choice (solve(i+1, cap-wt)), preventing item reuse.
  • • Swapping loop order in Coin Change II (accidentally counting permutations instead of combinations).
  • • Using 0 base initialization for minimum exact fill problems instead of infinity (INF).
  • • Confusing Coin Change (min coins DP) with Coin Change II (combinations count DP).
  • • Assuming item weight = 0 can be handled safely without checking for infinite value recursion loops.
  • • Attempting 1D capacity DP when target W = 10^9 without checking constraint bounds (Memory Limit Exceeded).
  • • Assuming unlimited item supply means infinite answer (capacity W still bounds maximum usable copies).

Interview Rules

Reusable item supply + capacity limit? → Unbounded Knapsack DP
Rule 1                                  → Define dp[w] state meaning explicitly
Rule 2                                  → Recursive TAKE stays on same item: solve(i, cap - wt)
Rule 3                                  → 2D Matrix TAKE reads SAME ROW: dp[i][w-wt]
Rule 4                                  → 1D Array: Loop capacity w = wt up to W (Left-to-Right / Forwards)
Rule 5                                  → Coin Change II = Outer coin loop; Combination Sum IV = Outer amount loop
Rule 6                                  → Rod Cutting = Unbounded Knapsack with piece length = weight
Time Complexity                         → O(N * Capacity) (Pseudo-Polynomial)
Space Complexity                        → O(N * Capacity) 2D Table or O(Capacity) 1D Array

💡 Golden Rule: If an item can be used again after taking it, keep that item available and update capacity forward so the current iteration may reuse its newly computed states.

Production Thinking

Package Sizing & Shipping → Unbounded Knapsack models optimal container filling using reusable standard box sizes.

Cloud Resource Bundle Purchasing → unlimited supply DP determines cheapest combination of cloud server bundles to reach required RAM/CPU.

Manufacturing Material Cutting → Rod Cutting models optimal raw material slicing into reusable standard sellable lengths.

Production warning → validate item weight > 0 before running unbounded loops; zero-weight positive items cause infinite revenue loops.

Remember This

Unbounded Rule: Item reusable UNLIMITED times
Recursive TAKE: solve(i, cap - wt) (Stay on index i)
1D Array: Loop Capacity LEFT-TO-RIGHT (w = wt up to W)
2D Matrix: TAKE reads Same Row dp[i][w-wt]
Rod Cutting: Capacity = Rod Length, Weight = Piece Length, Value = Price
Coin Change: Min coins DP (dp[0]=0, others INF)
Coin Change II: Combinations count (Outer coin loop)