Skip to content

Feat(Improved): Add 0/1 Knapsack Problem: Recursive and Tabulation (Bottom-Up DP) Implementations in Java along with their corresponding Tests #6425

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jul 22, 2025
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,78 @@
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;

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));
}

@Test
public void negativeCapacity() {
int[] values = {10, 20, 30};
int[] weights = {1, 1, 1};
int capacity = -10;
int itemCount = values.length;

IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> KnapsackZeroOneTabulation.compute(values, weights, capacity, itemCount));
assertEquals("Capacity must not be negative.", exception.getMessage());
}

@Test
public void mismatchedLengths() {
int[] values = {60, 100}; // Only 2 values
int[] weights = {10, 20, 30}; // 3 weights
int capacity = 50;
int itemCount = 2; // Matches `values.length`

// You could either expect 0 or throw an IllegalArgumentException in your compute function
assertThrows(IllegalArgumentException.class, () -> { KnapsackZeroOneTabulation.compute(values, weights, capacity, itemCount); });
}

@Test
public void nullInputs() {
int[] weights = {1, 2, 3};
int capacity = 10;
int itemCount = 3;

IllegalArgumentException exception1 = assertThrows(IllegalArgumentException.class, () -> KnapsackZeroOneTabulation.compute(null, weights, capacity, itemCount));
assertEquals("Values and weights arrays must not be null.", exception1.getMessage());

int[] values = {1, 2, 3};

IllegalArgumentException exception2 = assertThrows(IllegalArgumentException.class, () -> KnapsackZeroOneTabulation.compute(values, null, capacity, itemCount));
assertEquals("Values and weights arrays must not be null.", exception2.getMessage());
}
}
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));
}
}
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy