def knapsackbottomup(weights: List[int], values: List[int],
"""
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.
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"}