Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.thealgorithms.dynamicprogramming;

/**
* The {@code KnapsackZeroOne} provides Recursive solution for the 0/1 Knapsack
* problem. Solves by exploring all combinations of items using recursion. No
* memoization or dynamic programming optimizations are applied.
*
* Time Complexity: O(2^n) — explores all subsets.
* Space Complexity: O(n) — due to recursive call stack.
*
* Problem Reference: https://en.wikipedia.org/wiki/Knapsack_problem
*/
public final class KnapsackZeroOne {

private KnapsackZeroOne() {
// Prevent instantiation
}

/**
* Solves the 0/1 Knapsack problem using recursion.
*
* @param values the array containing values of the items
* @param weights the array containing weights of the items
* @param capacity the total capacity of the knapsack
* @param n the number of items
* @return the maximum total value achievable within the given weight limit
* @throws IllegalArgumentException if input arrays are null, empty, or
* lengths mismatch
*/
public static int compute(final int[] values, final int[] weights, final int capacity, final int n) {
if (values == null || weights == null) {
throw new IllegalArgumentException("Input arrays cannot be null.");
}
if (values.length != weights.length) {
throw new IllegalArgumentException("Value and weight arrays must be of the same length.");
}
if (capacity < 0 || n < 0) {
throw new IllegalArgumentException("Invalid input: arrays must be non-empty and capacity/n "
+ "non-negative.");
}
if (n == 0 || capacity == 0 || values.length == 0) {
return 0;
}

if (weights[n - 1] <= capacity) {
final int include = values[n - 1] + compute(values, weights, capacity - weights[n - 1], n - 1);
final int exclude = compute(values, weights, capacity, n - 1);
return Math.max(include, exclude);
} else {
return compute(values, weights, capacity, n - 1);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.thealgorithms.dynamicprogramming;

/**
* Tabulation (Bottom-Up) Solution for 0-1 Knapsack Problem.
* This method uses dynamic programming to build up a solution iteratively,
* filling a 2-D array where each entry dp[i][w] represents the maximum value
* achievable with the first i items and a knapsack capacity of w.
*
* The tabulation approach is efficient because it avoids redundant calculations
* by solving all subproblems in advance and storing their results, ensuring
* each subproblem is solved only once. This is a key technique in dynamic programming,
* making it possible to solve problems that would otherwise be infeasible due to
* exponential time complexity in naive recursive solutions.
*
* Time Complexity: O(n * W), where n is the number of items and W is the knapsack capacity.
* Space Complexity: O(n * W) for the DP table.
*
* For more information, see:
* https://en.wikipedia.org/wiki/Knapsack_problem#Dynamic_programming
*/
public final class KnapsackZeroOneTabulation {

private KnapsackZeroOneTabulation() {
// Prevent instantiation
}

/**
* Solves the 0-1 Knapsack problem using the bottom-up tabulation technique.
* @param values the values of the items
* @param weights the weights of the items
* @param capacity the total capacity of the knapsack
* @param itemCount the number of items
* @return the maximum value that can be put in the knapsack
* @throws IllegalArgumentException if input arrays are null, of different lengths,or if capacity or itemCount is invalid
*/
public static int compute(final int[] values, final int[] weights, final int capacity, final int itemCount) {
if (values == null || weights == null) {
throw new IllegalArgumentException("Values and weights arrays must not be null.");
}
if (values.length != weights.length) {
throw new IllegalArgumentException("Values and weights arrays must be non-null and of same length.");
}
if (capacity < 0) {
throw new IllegalArgumentException("Capacity must not be negative.");
}
if (itemCount < 0 || itemCount > values.length) {
throw new IllegalArgumentException("Item count must be between 0 and the length of the values array.");
}

final int[][] dp = new int[itemCount + 1][capacity + 1];

for (int i = 1; i <= itemCount; i++) {
final int currentValue = values[i - 1];
final int currentWeight = weights[i - 1];

for (int w = 1; w <= capacity; w++) {
if (currentWeight <= w) {
final int includeItem = currentValue + dp[i - 1][w - currentWeight];
final int excludeItem = dp[i - 1][w];
dp[i][w] = Math.max(includeItem, excludeItem);
} else {
dp[i][w] = dp[i - 1][w];
}
}
}

return dp[itemCount][capacity];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.thealgorithms.dynamicprogramming;

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;

public class KnapsackZeroOneTabulationTest {

@Test
public void basicCheck() {
int[] values = {60, 100, 120};
int[] weights = {10, 20, 30};
int capacity = 50;
int itemCount = values.length;

int expected = 220; // Best choice: item 1 (100) and item 2 (120)
int result = KnapsackZeroOneTabulation.compute(values, weights, capacity, itemCount);
assertEquals(expected, result);
}

@Test
public void emptyKnapsack() {
int[] values = {};
int[] weights = {};
int capacity = 50;
int itemCount = 0;

assertEquals(0, KnapsackZeroOneTabulation.compute(values, weights, capacity, itemCount));
}

@Test
public void zeroCapacity() {
int[] values = {60, 100, 120};
int[] weights = {10, 20, 30};
int capacity = 0;
int itemCount = values.length;

assertEquals(0, KnapsackZeroOneTabulation.compute(values, weights, capacity, itemCount));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.thealgorithms.dynamicprogramming;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import org.junit.jupiter.api.Test;

class KnapsackZeroOneTest {

@Test
void basicCheck() {
int[] values = {60, 100, 120};
int[] weights = {10, 20, 30};
int capacity = 50;
int expected = 220;

int result = KnapsackZeroOne.compute(values, weights, capacity, values.length);
assertEquals(expected, result);
}

@Test
void zeroCapacity() {
int[] values = {10, 20, 30};
int[] weights = {1, 1, 1};
int capacity = 0;

int result = KnapsackZeroOne.compute(values, weights, capacity, values.length);
assertEquals(0, result);
}

@Test
void zeroItems() {
int[] values = {};
int[] weights = {};
int capacity = 10;

int result = KnapsackZeroOne.compute(values, weights, capacity, 0);
assertEquals(0, result);
}

@Test
void weightsExceedingCapacity() {
int[] values = {10, 20};
int[] weights = {100, 200};
int capacity = 50;

int result = KnapsackZeroOne.compute(values, weights, capacity, values.length);
assertEquals(0, result);
}

@Test
void throwsOnNullArrays() {
assertThrows(IllegalArgumentException.class, () -> KnapsackZeroOne.compute(null, new int[] {1}, 10, 1));
assertThrows(IllegalArgumentException.class, () -> KnapsackZeroOne.compute(new int[] {1}, null, 10, 1));
}

@Test
void throwsOnMismatchedArrayLengths() {
assertThrows(IllegalArgumentException.class, () -> KnapsackZeroOne.compute(new int[] {10, 20}, new int[] {5}, 15, 2));
}

@Test
void throwsOnNegativeInputs() {
assertThrows(IllegalArgumentException.class, () -> KnapsackZeroOne.compute(new int[] {10}, new int[] {5}, -1, 1));

assertThrows(IllegalArgumentException.class, () -> KnapsackZeroOne.compute(new int[] {10}, new int[] {5}, 5, -1));
}
}