0-1 Knapsack with Gold Bars
Problem
You are given a knapsack capacity W and n gold bars with weights w[0..n-1]. Each bar can be taken at most once (no repetitions, no fractional taking). Every bar's value equals its weight. Select a subset of bars whose total weight does not exceed W and is as large as possible. Return that maximum achievable weight.
This is the 0-1 Knapsack Problem specialized to the case value[i] = weight[i]. In this special case, maximizing total value is equivalent to maximizing total weight—so it also aligns with the optimization version of the classic [Subset Sum Problem](subset-sum-1-is-there-a-subset-) (find the largest sum ≤ W).
Input / Output
- Input: capacity
W, number of barsn, followed by thenweights. - Output: the largest total weight ≤
Wobtainable using each bar at most once.
Examples
Example 1
Input:
W = 10, n = 3
weights = [1, 4, 8]
Output: 9
Explanation: Best subset is {1, 8} totaling 9 (cannot reach 10 exactly).
Relation to Other Problems
- General 0-1 Knapsack: This is the case where values == weights.
- Subset Sum (decision): asks if we can reach exactly
W; here we seek the maximum ≤W. - Partition Equal Subset Sum: special subset sum aiming for
total/2.
Solution
Let dp[i][c] = maximum achievable weight ≤ c using the first i items. Since value = weight, transition only adds weight.
Method 1 - Naive Recursion (Exponential)
Intuition
For each item decide: include it (if it fits) or exclude it. Explore all subsets; track best sum ≤ W.
Approach
Recursive function solve(i, remaining) returns best weight using items i..n-1 with capacity remaining:
- If
i == norremaining == 0: return 0. - Option 1: skip ->
skip = solve(i+1, remaining). - Option 2: take if
w[i] ≤ remaining->take = w[i] + solve(i+1, remaining - w[i]). - Return
max(skip, take).
Code
C++
#include <vector>
using namespace std;
long long maxGoldRec(const vector<int>& w, int i, int cap) {
if (i == (int)w.size() || cap == 0) return 0;
long long skip = maxGoldRec(w, i+1, cap);
if (w[i] > cap) return skip;
long long take = w[i] + maxGoldRec(w, i+1, cap - w[i]);
return max(skip, take);
}
long long takeMaxGoldRec(const vector<int>& w, int W) { return maxGoldRec(w, 0, W); }
Go
package main
func maxGoldRec(w []int, i, cap int) int {
if i == len(w) || cap == 0 { return 0 }
skip := maxGoldRec(w, i+1, cap)
if w[i] > cap { return skip }
take := w[i] + maxGoldRec(w, i+1, cap-w[i])
if take > skip { return take }
return skip
}
func TakeMaxGoldRec(w []int, W int) int { return maxGoldRec(w, 0, W) }
Java
import java.util.*;
class GoldRec {
int dfs(int[] w, int i, int cap) {
if (i == w.length || cap == 0) return 0;
int skip = dfs(w, i+1, cap);
if (w[i] > cap) return skip;
int take = w[i] + dfs(w, i+1, cap - w[i]);
return Math.max(skip, take);
}
int takeMaxGold(int[] w, int W) { return dfs(w, 0, W); }
}
Python
from typing import List
def _gold_rec(w: List[int], i: int, cap: int) -> int:
if i == len(w) or cap == 0:
return 0
skip = _gold_rec(w, i+1, cap)
if w[i] > cap:
return skip
take = w[i] + _gold_rec(w, i+1, cap - w[i])
return max(skip, take)
def take_max_gold_rec(w: List[int], W: int) -> int:
return _gold_rec(w, 0, W)
Complexity
- ⏰ Time complexity:
O(2^n)– explores all subsets. - 🧺 Space complexity:
O(n)– recursion depth.
Method 2 - Top-Down Memoization
Intuition
Store results for (i, cap) to avoid re-exploring the same state.
Approach
Use 2D memo array (size n x (W+1)) initialized to -1. Standard recursion; fill memo.
Recurrence
F(i, c) = max(F(i+1, c), w[i] + F(i+1, c - w[i])) if w[i] ≤ c, else F(i, c) = F(i+1, c).
Code
C++
#include <vector>
long long dfsMemo(const std::vector<int>& w, int i, int cap, std::vector<std::vector<long long>>& memo){
if (i == (int)w.size() || cap == 0) return 0;
long long &res = memo[i][cap];
if (res != -1) return res;
long long skip = dfsMemo(w, i+1, cap, memo);
if (w[i] > cap) return res = skip;
long long take = w[i] + dfsMemo(w, i+1, cap - w[i], memo);
return res = max(skip, take);
}
long long takeMaxGoldMemo(const std::vector<int>& w, int W){
std::vector<std::vector<long long>> memo(w.size(), std::vector<long long>(W+1, -1));
return dfsMemo(w, 0, W, memo);
}
Go
func TakeMaxGoldMemo(w []int, W int) int {
memo := make([][]int, len(w))
for i := range memo { memo[i] = make([]int, W+1); for c := 0; c <= W; c++ { memo[i][c] = -1 } }
var dfs func(int,int) int
dfs = func(i, cap int) int {
if i == len(w) || cap == 0 { return 0 }
if memo[i][cap] != -1 { return memo[i][cap] }
skip := dfs(i+1, cap)
if w[i] > cap { memo[i][cap] = skip; return skip }
take := w[i] + dfs(i+1, cap-w[i])
if take > skip { memo[i][cap] = take } else { memo[i][cap] = skip }
return memo[i][cap]
}
return dfs(0, W)
}
Java
class GoldMemo {
int[][] memo;
int dfs(int[] w, int i, int cap) {
if (i == w.length || cap == 0) return 0;
if (memo[i][cap] != -1) return memo[i][cap];
int skip = dfs(w, i+1, cap);
if (w[i] > cap) return memo[i][cap] = skip;
int take = w[i] + dfs(w, i+1, cap - w[i]);
return memo[i][cap] = Math.max(skip, take);
}
int takeMaxGold(int[] w, int W) {
memo = new int[w.length][W+1];
for (int i = 0; i < w.length; i++) java.util.Arrays.fill(memo[i], -1);
return dfs(w, 0, W);
}
}
Python
from functools import lru_cache
from typing import List
def take_max_gold_memo(w: List[int], W: int) -> int:
@lru_cache(None)
def f(i: int, cap: int) -> int:
if i == len(w) or cap == 0:
return 0
skip = f(i+1, cap)
if w[i] > cap:
return skip
take = w[i] + f(i+1, cap - w[i])
return max(skip, take)
return f(0, W)
Complexity
- ⏰ Time complexity:
O(n * W)– each(i, cap)solved once. - 🧺 Space complexity:
O(n * W)– memo + recursion stackO(n).
Method 3 - Bottom-Up 2D DP
Intuition
Tabulate solutions for increasing number of items and capacities.
Approach
dp[i][c] = best weight using first i items with capacity c.
Transition: if w[i-1] ≤ c, dp[i][c] = max(dp[i-1][c], w[i-1] + dp[i-1][c - w[i-1]]) else copy dp[i-1][c].
Code
C++
#include <vector>
long long takeMaxGoldBU2D(const std::vector<int>& w, int W){
int n = (int)w.size();
std::vector<std::vector<long long>> dp(n+1, std::vector<long long>(W+1, 0));
for (int i = 1; i <= n; ++i){
for (int c = 0; c <= W; ++c){
dp[i][c] = dp[i-1][c];
if (w[i-1] <= c){
dp[i][c] = std::max(dp[i][c], (long long)w[i-1] + dp[i-1][c - w[i-1]]);
}
}
}
return dp[n][W];
}
Go
func TakeMaxGoldBU2D(w []int, W int) int {
n := len(w)
dp := make([][]int, n+1)
for i := range dp { dp[i] = make([]int, W+1) }
for i := 1; i <= n; i++ {
for c := 0; c <= W; c++ {
dp[i][c] = dp[i-1][c]
if w[i-1] <= c {
cand := w[i-1] + dp[i-1][c-w[i-1]]
if cand > dp[i][c] { dp[i][c] = cand }
}
}
}
return dp[n][W]
}
Java
class GoldBU2D {
int takeMaxGold(int[] w, int W) {
int n = w.length;
int[][] dp = new int[n+1][W+1];
for (int i = 1; i <= n; i++) {
for (int c = 0; c <= W; c++) {
dp[i][c] = dp[i-1][c];
if (w[i-1] <= c) {
dp[i][c] = Math.max(dp[i][c], w[i-1] + dp[i-1][c - w[i-1]]);
}
}
}
return dp[n][W];
}
}
Python
def take_max_gold_bu_2d(w, W):
n = len(w)
dp = [[0]*(W+1) for _ in range(n+1)]
for i in range(1, n+1):
wi = w[i-1]
for c in range(W+1):
dp[i][c] = dp[i-1][c]
if wi <= c:
cand = wi + dp[i-1][c-wi]
if cand > dp[i][c]:
dp[i][c] = cand
return dp[n][W]
Complexity
- ⏰ Time complexity:
O(n * W)– nested loops. - 🧺 Space complexity:
O(n * W)– 2D table.
Method 4 - Bottom-Up 1D Space Optimization
Intuition
Transition for current item depends only on previous row; iterate capacity descending to avoid reuse of an item twice.
Approach
dp[c]= best weight for capacityc.- For each item
wi, loopcfromWdown towiupdatingdp[c] = max(dp[c], wi + dp[c - wi]).
Code
C++
#include <vector>
long long takeMaxGoldBU1D(const std::vector<int>& w, int W){
std::vector<long long> dp(W+1, 0);
for (int wi : w){
for (int c = W; c >= wi; --c){
dp[c] = std::max(dp[c], (long long)wi + dp[c - wi]);
}
}
return dp[W];
}
Go
func TakeMaxGoldBU1D(w []int, W int) int {
dp := make([]int, W+1)
for _, wi := range w {
for c := W; c >= wi; c-- {
cand := wi + dp[c-wi]
if cand > dp[c] { dp[c] = cand }
}
}
return dp[W]
}
Java
class GoldBU1D {
int takeMaxGold(int[] w, int W) {
int[] dp = new int[W+1];
for (int wi : w) {
for (int c = W; c >= wi; c--) {
dp[c] = Math.max(dp[c], wi + dp[c - wi]);
}
}
return dp[W];
}
}
Python
def take_max_gold_bu_1d(w, W):
dp = [0]*(W+1)
for wi in w:
for c in range(W, wi-1, -1):
cand = wi + dp[c-wi]
if cand > dp[c]:
dp[c] = cand
return dp[W]
Complexity
- ⏰ Time complexity:
O(n * W)– same state count. - 🧺 Space complexity:
O(W)– single array.
Method Comparison
| Method | Time | Space | Notes |
|---|---|---|---|
| 1 Naive Recursion | O(2^n) | O(n) | Exponential baseline |
| 2 Memoized | O(n * W) | O(n * W) | Practical top-down |
| 3 Bottom-Up 2D | O(n * W) | O(n * W) | Classic tabulation |
| 4 Bottom-Up 1D | O(n * W) | O(W) | Preferred optimized |
Notes
- If you need to reconstruct which bars are chosen, store predecessor info (e.g., keep a separate
take[i][c]boolean in the 2D version, then backtrack). For the 1D optimized version, reconstruct by re-running with reverse reasoning or keeping additional tracking. - Because value equals weight, returns are always ≤
Wand monotonic inW. - This file links to the general [0-1 Knapsack Problem](0-1-knapsack-problem) for the broader formulation with independent value array.