diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOne.java b/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOne.java new file mode 100644 index 000000000000..abc1e321ca8f --- /dev/null +++ b/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOne.java @@ -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); + } + } +} diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTabulation.java b/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTabulation.java new file mode 100644 index 000000000000..c560efc61c71 --- /dev/null +++ b/src/main/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTabulation.java @@ -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]; + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTabulationTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTabulationTest.java new file mode 100644 index 000000000000..6c61ad2130e3 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTabulationTest.java @@ -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()); + } +} diff --git a/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTest.java b/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTest.java new file mode 100644 index 000000000000..0ca2b91fc273 --- /dev/null +++ b/src/test/java/com/thealgorithms/dynamicprogramming/KnapsackZeroOneTest.java @@ -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)); + } +}
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: