◐ Off-By-One · answer catalog

algo-knapsack-01-dp

1 answer(s)pythonpython3

def knapsackbottomup(weights: List[int], values: List[int],

📦 Source in repository (JSON)

Answer

"""
0/1 Knapsack — Dynamic Programming
===================================
Two approaches: top-down memoization and bottom-up DP.
Both return (max_value, selected_indices) where selected_indices lists
the original indices (0-based) of items chosen for the optimal knapsack.
"""

from functools import lru_cache
from typing import List, Tuple


# ── 1. Bottom-Up DP (2D table) ──────────────────────────

def knapsack_bottom_up(weights: List[int], values: List[int],
                       capacity: int) -> Tuple[int, List[int]]:
    n = len(weights)
    dp = [[0] * (capacity + 1) for _ in range(n + 1)]

    for i in range(1, n + 1):
        w_i, v_i = weights[i - 1], values[i - 1]
        for c in range(capacity + 1):
            if w_i > c:
                dp[i][c] = dp[i - 1][c]
            else:
                dp[i][c] = max(dp[i - 1][c],
                               dp[i - 1][c - w_i] + v_i)

    # Reconstruct selected items
    selected, c = [], capacity
    for i in range(n, 0, -1):
        if dp[i][c] != dp[i - 1][c]:        # item i-1 was taken
            selected.append(i - 1)
            c -= weights[i - 1]
    selected.reverse()
    return dp[n][capacity], selected


# ── 2. Bottom-Up DP (1D space, O(W)) ───────────────────

def knapsack_bottom_up_1d(weights: List[int], values: List[int],
                          capacity: int) -> Tuple[int, List[int]]:
    n = len(weights)
    dp = [0] * (capacity + 1)
    keep = [[False] * (capacity + 1) for _ in range(n)]

    for i in range(n):
        w_i, v_i = weights[i], values[i]
        for c in range(capacity, w_i - 1, -1):       # backwards!
            take = dp[c - w_i] + v_i
            if take > dp[c]:
                dp[c] = take
                keep[i][c] = True

    selected, c = [], capacity
    for i in range(n - 1, -1, -1):
        if keep[i][c]:
            selected.append(i)
            c -= weights[i]
    selected.reverse()
    return dp[capacity], selected


# ── 3. Top-Down Memoization ────────────────────────────

def knapsack_top_down(weights: List[int], values: List[int],
                      capacity: int) -> Tuple[int, List[int]]:
    n = len(weights)

    @lru_cache(maxsize=None)
    def dp(i: int, cap: int) -> int:
        """Max value using items i..n-1 with capacity cap."""
        if i >= n or cap <= 0:
            return 0
        best = dp(i + 1, cap)                # skip
        if weights[i] <= cap:                # take (if fits)
            best = max(best, dp(i + 1, cap - weights[i]) + values[i])
        return best

    selected, cap = [], capacity
    for i in range(n):
        if weights[i] <= cap:
            without = dp(i + 1, cap)
            with_it = dp(i + 1, cap - weights[i]) + values[i]
            if with_it > without:            # strictly better → take it
                selected.append(i)
                cap -= weights[i]
    return dp(0, capacity), selected

Key ideas:

Approach State definition Complexity
Bottom-up 2D dp[i][c] = best value using first i items, capacity c O(n·W) time, O(n·W) space
Bottom-up 1D single array, iterate capacity backwards to avoid reuse O(n·W) time, O(W) space
Top-down dp(i, cap) = best value from item i onward with cap left O(n·W) time, O(n·W) stack

Reconstruction: Walk the decision table backwards. For bottom-up: whenever dp[i][c] ≠ dp[i-1][c], item i-1 was taken. For top-down: at each step, compare the value with vs without the item — if taking gives strictly more, it's selected.


Evidence & signatures

The test suite in the full source (`knapsack.py`) verifies:

| Test case | What it checks |
|---|---|
| **Classic CLRS** (w=[10,20,30], v=[60,100,120], cap=50) | Value 220, picks items 1 & 2 |
| **Single item fits** (w=[5], v=[10], cap=10) | Value 10, picks item 0 |
| **Single item doesn't fit** (w=[5], v=[10], cap=3) | Value 0, empty selection |
| **All items fit** (w=[2,3,1], v=[5,7,3], cap=10) | Value 15, all items |
| **Zero capacity** | Value 0, empty selection |
| **Empty item list** | Value 0, empty selection |
| **Weight/value trade-off** (w=[1,10], v=[100,1], cap=10) | Takes the light valuable item (value 100) |
| **Random stress** (n=1..50, random weights/values/capacities) | All 4 methods agree on optimal value |
| **Exhaustive brute-force** (n=0..12, all subsets enumerated) | All DP methods match exact ground truth |

All four implementations agree on optimal **value** across every test. When multiple optimal solutions exist (same value, different item sets), tie-breaking may differ — the code uses strict inequality (`>`) during reconstruction, preferring earlier items in ties.

---
{"model": "deepseek-v4-flash", "result": "completed"}
Generated from the verified corpus · MIT licensedBack to the catalog