Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 78 additions & 52 deletions src/main/java/com/williamfiset/algorithms/dp/KnapsackUnbounded.java
Original file line number Diff line number Diff line change
@@ -1,92 +1,118 @@
package com.williamfiset.algorithms.dp;

/**
* This file contains a dynamic programming solutions to the classic unbounded knapsack problem
* where are you are trying to maximize the total profit of items selected without exceeding the
* capacity of your knapsack.
* Unbounded Knapsack Problem — Bottom-Up Dynamic Programming
*
* Given n items, each with a weight and a value, determine the maximum total
* value that can be placed in a knapsack of a given capacity. Unlike the 0/1
* knapsack, each item may be selected unlimited times.
*
* Two implementations are provided:
*
* <p>Version 1: Time Complexity: O(nW) Version 1 Space Complexity: O(nW)
* 1. unboundedKnapsack() — 2D DP table, O(n*W) time and space
* 2. unboundedKnapsackSpaceEfficient() — 1D DP array, O(n*W) time, O(W) space
*
* <p>Version 2: Time Complexity: O(nW) Space Complexity: O(W)
* The key difference from 0/1 knapsack is in the recurrence: when including
* item i, we look at dp[i][sz - w] (same row) instead of dp[i-1][sz - w]
* (previous row), allowing the item to be selected again.
*
* <p>Tested code against: https://www.hackerrank.com/challenges/unbounded-knapsack
* See also: Knapsack_01 for the variant where each item can be used at most once.
*
* Tested against: https://www.hackerrank.com/challenges/unbounded-knapsack
*
* Time: O(n*W) where n = number of items, W = capacity
* Space: O(n*W) or O(W) for the space-efficient version
*
* @author William Fiset, william.alexandre.fiset@gmail.com
*/
package com.williamfiset.algorithms.dp;

public class KnapsackUnbounded {

// ==================== Implementation 1: 2D DP table ====================

/**
* @param maxWeight - The maximum weight of the knapsack
* @param W - The weights of the items
* @param V - The values of the items
* @return The maximum achievable profit of selecting a subset of the elements such that the
* capacity of the knapsack is not exceeded
* Computes the maximum value achievable with unlimited item reuse.
*
* dp[i][sz] = max value using items 1..i with capacity sz.
* When including item i, we reference dp[i][sz-w] (not dp[i-1][sz-w])
* because the item can be selected again.
*
* @param maxWeight the maximum weight the knapsack can hold
* @param W array of item weights
* @param V array of item values
* @return the maximum total value
*
* Time: O(n*W)
* Space: O(n*W)
*/
public static int unboundedKnapsack(int maxWeight, int[] W, int[] V) {

if (W == null || V == null || W.length != V.length || maxWeight < 0)
throw new IllegalArgumentException("Invalid input");

final int N = W.length;
final int n = W.length;
int[][] dp = new int[n + 1][maxWeight + 1];

// Initialize a table where individual rows represent items
// and columns represent the weight of the knapsack
int[][] DP = new int[N + 1][maxWeight + 1];

// Loop through items
for (int i = 1; i <= N; i++) {

// Get the value and weight of the item
for (int i = 1; i <= n; i++) {
int w = W[i - 1], v = V[i - 1];

// Consider all possible knapsack sizes
for (int sz = 1; sz <= maxWeight; sz++) {
// Include item i (reuse allowed — look at same row dp[i])
if (sz >= w)
dp[i][sz] = dp[i][sz - w] + v;

// Try including the current element
if (sz >= w) DP[i][sz] = DP[i][sz - w] + v;

// Check if not selecting this item at all is more profitable
if (DP[i - 1][sz] > DP[i][sz]) DP[i][sz] = DP[i - 1][sz];
// Skip item i if that's more profitable
if (dp[i - 1][sz] > dp[i][sz])
dp[i][sz] = dp[i - 1][sz];
}
}

// Return the best value achievable
return DP[N][maxWeight];
return dp[n][maxWeight];
}

public static int unboundedKnapsackSpaceEfficient(int maxWeight, int[] W, int[] V) {
// ==================== Implementation 2: Space-efficient 1D DP ====================

/**
* Space-efficient version using a single 1D array.
*
* dp[sz] = max value achievable with capacity sz using any items.
* For each capacity, try every item and keep the best.
*
* @param maxWeight the maximum weight the knapsack can hold
* @param W array of item weights
* @param V array of item values
* @return the maximum total value
*
* Time: O(n*W)
* Space: O(W)
*/
public static int unboundedKnapsackSpaceEfficient(int maxWeight, int[] W, int[] V) {
if (W == null || V == null || W.length != V.length)
throw new IllegalArgumentException("Invalid input");

final int N = W.length;

// Initialize a table where we will only keep track of
// the best possible value for each knapsack weight
int[] DP = new int[maxWeight + 1];
final int n = W.length;
int[] dp = new int[maxWeight + 1];

// Consider all possible knapsack sizes
for (int sz = 1; sz <= maxWeight; sz++) {

// Loop through items
for (int i = 0; i < N; i++) {

// First check that we can include this item (we can't include it if
// it's too heavy for our knapsack). Assumming it fits inside the
// knapsack check if including this element would be profitable.
if (sz - W[i] >= 0 && DP[sz - W[i]] + V[i] > DP[sz]) DP[sz] = DP[sz - W[i]] + V[i];
for (int i = 0; i < n; i++) {
// Include item i if it fits and improves the value
if (sz >= W[i] && dp[sz - W[i]] + V[i] > dp[sz])
dp[sz] = dp[sz - W[i]] + V[i];
}
}

// Return the best value achievable
return DP[maxWeight];
return dp[maxWeight];
}

public static void main(String[] args) {

int[] W = {3, 6, 2};
int[] V = {5, 20, 3};
int knapsackValue = unboundedKnapsackSpaceEfficient(10, W, V);
System.out.println("Maximum knapsack value: " + knapsackValue);

// Capacity 10: best is (w=6,v=20) + 2x(w=2,v=3) = weight 10, value 26
System.out.println("2D DP: " + unboundedKnapsack(10, W, V)); // 26

// Space-efficient: same result
System.out.println("Space-efficient: " + unboundedKnapsackSpaceEfficient(10, W, V)); // 26

// Capacity 12: two items of weight 6 and value 20 = 40
System.out.println("2D DP (cap=12): " + unboundedKnapsack(12, W, V)); // 40
System.out.println("Space (cap=12): " + unboundedKnapsackSpaceEfficient(12, W, V)); // 40
}
}
153 changes: 101 additions & 52 deletions src/main/java/com/williamfiset/algorithms/dp/Knapsack_01.java
Original file line number Diff line number Diff line change
@@ -1,87 +1,136 @@
package com.williamfiset.algorithms.dp;

import java.util.LinkedList;
import java.util.List;

/**
* This file contains a dynamic programming solutions to the classic 0/1 knapsack problem where are
* you are trying to maximize the total profit of items selected without exceeding the capacity of
* your knapsack.
* 0/1 Knapsack Problem — Bottom-Up Dynamic Programming
*
* Given n items, each with a weight and a value, determine the maximum total
* value that can be placed in a knapsack of a given capacity. Each item may
* be selected at most once (hence "0/1").
*
* <p>Time Complexity: O(nW) Space Complexity: O(nW)
* The DP table dp[i][sz] represents the maximum value achievable using the
* first i items with a knapsack capacity of sz. For each item we either:
* - Skip it: dp[i][sz] = dp[i-1][sz]
* - Include it: dp[i][sz] = dp[i-1][sz - w] + v (if it fits)
*
* <p>Tested code against: https://open.kattis.com/problems/knapsack
* After filling the table, we backtrack to recover which items were selected:
* if dp[i][sz] != dp[i-1][sz], then item i was included.
*
* See also: KnapsackUnbounded for the variant where items can be reused.
*
* Tested against: https://open.kattis.com/problems/knapsack
*
* Time: O(n*W) where n = number of items, W = capacity
* Space: O(n*W)
*
* @author William Fiset, william.alexandre.fiset@gmail.com
*/
package com.williamfiset.algorithms.dp;

import java.util.ArrayList;
import java.util.List;

public class Knapsack_01 {

/**
* @param capacity - The maximum capacity of the knapsack
* @param W - The weights of the items
* @param V - The values of the items
* @return The maximum achievable profit of selecting a subset of the elements such that the
* capacity of the knapsack is not exceeded
* Computes the maximum value achievable without exceeding the knapsack capacity.
*
* @param capacity the maximum weight the knapsack can hold
* @param W array of item weights
* @param V array of item values
* @return the maximum total value
*
* Time: O(n*W)
* Space: O(n*W)
*/
public static int knapsack(int capacity, int[] W, int[] V) {

if (W == null || V == null || W.length != V.length || capacity < 0)
throw new IllegalArgumentException("Invalid input");

final int N = W.length;
final int n = W.length;

// Initialize a table where individual rows represent items
// and columns represent the weight of the knapsack
int[][] DP = new int[N + 1][capacity + 1];
// dp[i][sz] = max value using first i items with capacity sz
int[][] dp = new int[n + 1][capacity + 1];

for (int i = 1; i <= N; i++) {

// Get the value and weight of the item
for (int i = 1; i <= n; i++) {
int w = W[i - 1], v = V[i - 1];

for (int sz = 1; sz <= capacity; sz++) {
// Option 1: skip this item
dp[i][sz] = dp[i - 1][sz];

// Consider not picking this element
DP[i][sz] = DP[i - 1][sz];

// Consider including the current element and
// see if this would be more profitable
if (sz >= w && DP[i - 1][sz - w] + v > DP[i][sz]) DP[i][sz] = DP[i - 1][sz - w] + v;
// Option 2: include this item if it fits and improves the value
if (sz >= w && dp[i - 1][sz - w] + v > dp[i][sz])
dp[i][sz] = dp[i - 1][sz - w] + v;
}
}

int sz = capacity;
List<Integer> itemsSelected = new ArrayList<>();
return dp[n][capacity];
}

// Using the information inside the table we can backtrack and determine
// which items were selected during the dynamic programming phase. The idea
// is that if DP[i][sz] != DP[i-1][sz] then the item was selected
for (int i = N; i > 0; i--) {
if (DP[i][sz] != DP[i - 1][sz]) {
int itemIndex = i - 1;
itemsSelected.add(itemIndex);
sz -= W[itemIndex];
/**
* Returns the indices of items selected in the optimal solution.
*
* After filling the DP table, we recover the selected items by walking
* backwards from dp[n][capacity]. At each row i, we check:
*
* - If dp[i][sz] != dp[i-1][sz], then item i-1 contributed to the
* optimal value at this capacity, so it was selected. We add it
* to the result and reduce the remaining capacity by its weight.
*
* - If dp[i][sz] == dp[i-1][sz], then item i-1 was NOT selected
* (the optimal value came from the previous items alone), so we
* just move to row i-1.
*
* @param capacity the maximum weight the knapsack can hold
* @param W array of item weights
* @param V array of item values
* @return list of selected item indices (0-based, in ascending order)
*
* Time: O(n*W)
* Space: O(n*W)
*/
public static List<Integer> knapsackItems(int capacity, int[] W, int[] V) {
if (W == null || V == null || W.length != V.length || capacity < 0)
throw new IllegalArgumentException("Invalid input");

final int n = W.length;
int[][] dp = new int[n + 1][capacity + 1];

for (int i = 1; i <= n; i++) {
int w = W[i - 1], v = V[i - 1];
for (int sz = 1; sz <= capacity; sz++) {
dp[i][sz] = dp[i - 1][sz];
if (sz >= w && dp[i - 1][sz - w] + v > dp[i][sz])
dp[i][sz] = dp[i - 1][sz - w] + v;
}
}

// Return the items that were selected
// java.util.Collections.reverse(itemsSelected);
// return itemsSelected;
// Backtrack through the table to find which items were selected.
// Starting at dp[n][capacity], walk backwards row by row:
// - dp[i][sz] != dp[i-1][sz] → item i-1 was included, reduce capacity
// - dp[i][sz] == dp[i-1][sz] → item i-1 was skipped, move on
// We walk backwards (high to low index), so inserting at the front
// of a LinkedList produces ascending order without a separate sort.
LinkedList<Integer> items = new LinkedList<>();
int sz = capacity;
for (int i = n; i > 0; i--) {
if (dp[i][sz] != dp[i - 1][sz]) {
items.addFirst(i - 1);
sz -= W[i - 1];
}
}

// Return the maximum profit
return DP[N][capacity];
return items;
}

public static void main(String[] args) {

int capacity = 10;
int[] V = {1, 4, 8, 5};
// Example 1: capacity=10, items: (w=3,v=1), (w=3,v=4), (w=5,v=8), (w=6,v=5)
int[] W = {3, 3, 5, 6};
System.out.println(knapsack(capacity, W, V));
int[] V = {1, 4, 8, 5};
System.out.println("Max value: " + knapsack(10, W, V)); // 12
System.out.println("Items: " + knapsackItems(10, W, V)); // [1, 2]

capacity = 7;
V = new int[] {2, 2, 4, 5, 3};
// Example 2: capacity=7, items: (w=3,v=2), (w=1,v=2), (w=3,v=4), (w=4,v=5), (w=2,v=3)
W = new int[] {3, 1, 3, 4, 2};
System.out.println(knapsack(capacity, W, V));
V = new int[] {2, 2, 4, 5, 3};
System.out.println("Max value: " + knapsack(7, W, V)); // 10
System.out.println("Items: " + knapsackItems(7, W, V)); // [1, 3, 4]
}
}
Loading
Loading