Skip to content

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

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

Closed
wants to merge 3 commits into from
Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.thealgorithms.dynamicprogramming;

/**
* The {@code ZeroOneKnapsack} class provides a method to solve the classic 0/1 Knapsack problem.
* It returns the maximum value that can be obtained by selecting items within the weight limit.
*
* Problem Description: Given weights and values of n items, put these items in a knapsack of capacity W
* such that the total value is maximized. You cannot break an item, either pick the complete item or don't pick it.
*
* https://en.wikipedia.org/wiki/Knapsack_problem
*/
public final class ZeroOneKnapsack {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about renaming all related classes to start with Knapsack?
This way, it will be easier to find and group all Knapsack implementations together in the project.

private ZeroOneKnapsack() {
}

/**
* 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
*/
public static int compute(int[] values, int[] weights, int capacity, int n) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making some parameters final is considered good practice.

It makes sense to add input validation here also.

if (n == 0 || capacity == 0) {
return 0;
}

if (weights[n - 1] <= capacity) {
int include = values[n - 1] + compute(values, weights, capacity - weights[n - 1], n - 1);
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,49 @@
package com.thealgorithms.dynamicprogramming;

/**
* The {@code ZeroOneKnapsackTab} class provides a method to solve the 0-1 Knapsack problem
* using dynamic programming (tabulation approach).
*
* <p>0-1 Knapsack Problem -
* Given weights and values of n items, and a maximum weight W,
* determine the maximum total value of items that can be included in the knapsack
* such that their total weight does not exceed W. Each item can be picked only once.
*
* Problem Link: https://www.geeksforgeeks.org/0-1-knapsack-problem-dp-10/
*/
public final class ZeroOneKnapsackTab {

private ZeroOneKnapsackTab() {
// prevent instantiation
}

/**
* Solves the 0-1 Knapsack problem using the bottom-up tabulation technique.
*
* @param val the values of the items
* @param wt the weights of the items
* @param W the total capacity of the knapsack
* @param n the number of items
* @return the maximum value that can be put in the knapsack
*/
public static int compute(int[] val, int[] wt, int W, int n) {
int[][] dp = new int[n + 1][W + 1];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes sense to add input validation here, such as checks for null or empty arrays. This helps prevent runtime errors and makes the code more robust and reliable. And tests for that.


for (int i = 1; i <= n; i++) {
int value = val[i - 1];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using the final keyword here and in the following lines where applicable - it helps improve code clarity and prevents unintended reassignment.

int weight = wt[i - 1];

for (int w = 1; w <= W; w++) {
if (weight <= w) {
int include = value + dp[i - 1][w - weight];
int exclude = dp[i - 1][w];
dp[i][w] = Math.max(include, exclude);
} else {
dp[i][w] = dp[i - 1][w];
}
}
}

return dp[n][W];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.thealgorithms.dynamicprogramming;

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

import org.junit.jupiter.api.Test;

/**
* Test class for {@code ZeroOneKnapsackTab}.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is redundant, as it merely restates what is already clear from the class structure.

*/
public class ZeroOneknapsackTabTest {

/**
* Tests the 0-1 Knapsack tabulation approach with known values.
*/
@Test
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider refactoring these test methods into a single parameterized test. Since all tests follow the same pattern calling compute with different inputs and asserting expected outputs - using JUnit’s parameterized tests would reduce code duplication and improve maintainability. It will also make adding new test cases easier and keep the test class cleaner.

There are already many examples of parameterized tests in the repository.

public void testKnownValues() {
int[] val = {60, 100, 120};
int[] wt = {10, 20, 30};
int W = 50;
int n = val.length;

// Expected result is 220 (items with weight 20 and 30)
assertEquals(220, ZeroOneKnapsackTab.compute(val, wt, W, n), "Maximum value for capacity 50 should be 220.");
}

@Test
public void testZeroCapacity() {
int[] val = {10, 20, 30};
int[] wt = {1, 1, 1};
int W = 0;
int n = val.length;

// With zero capacity, the result should be 0
assertEquals(0, ZeroOneKnapsackTab.compute(val, wt, W, n), "Maximum value for capacity 0 should be 0.");
}

@Test
public void testZeroItems() {
int[] val = {};
int[] wt = {};
int W = 10;
int n = val.length;

// With no items, the result should be 0
assertEquals(0, ZeroOneKnapsackTab.compute(val, wt, W, n), "Maximum value with no items should be 0.");
}

@Test
public void testExactFit() {
int[] val = {5, 10, 15};
int[] wt = {1, 2, 3};
int W = 6;
int n = val.length;

// All items fit exactly into capacity 6
assertEquals(30, ZeroOneKnapsackTab.compute(val, wt, W, n), "Maximum value for exact fit should be 30.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.thealgorithms.dynamicprogramming;

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

import org.junit.jupiter.api.Test;

/**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same suggestions for improvement also apply to this class.

* Test class for {@code ZeroOneKnapsack}.
*/
public class ZeroOneKnapsackTest {

/**
* Tests the knapsack computation for a basic example.
*/
@Test
public void testKnapsackBasic() {
int[] val = {15, 14, 10, 45, 30};
int[] wt = {2, 5, 1, 3, 4};
int W = 7;
assertEquals(75, ZeroOneKnapsack.compute(val, wt, W, val.length), "Expected maximum value is 75.");
}

/**
* Tests the knapsack computation when the knapsack capacity is zero.
*/
@Test
public void testZeroCapacity() {
int[] val = {10, 20, 30};
int[] wt = {1, 1, 1};
int W = 0;
assertEquals(0, ZeroOneKnapsack.compute(val, wt, W, val.length), "Expected maximum value is 0 for zero capacity.");
}

/**
* Tests the knapsack computation when there are no items.
*/
@Test
public void testNoItems() {
int[] val = {};
int[] wt = {};
int W = 10;
assertEquals(0, ZeroOneKnapsack.compute(val, wt, W, 0), "Expected maximum value is 0 when no items are available.");
}

/**
* Tests the knapsack computation when items exactly fit the capacity.
*/
@Test
public void testExactFit() {
int[] val = {60, 100, 120};
int[] wt = {10, 20, 30};
int W = 50;
assertEquals(220, ZeroOneKnapsack.compute(val, wt, W, val.length), "Expected maximum value is 220 for exact fit.");
}
}
Loading