Skip to content

Commit

Permalink
Add approaches for Knapsack (#2791)
Browse files Browse the repository at this point in the history
* Add approaches for Knapsack

* Fix formatting in introduction.md
  • Loading branch information
kahgoh authored May 6, 2024
1 parent ce49275 commit b9ce42a
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 0 deletions.
27 changes: 27 additions & 0 deletions exercises/practice/knapsack/.approaches/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"introduction": {
"authors": [
"kahgoh"
]
},
"approaches": [
{
"uuid": "bf439128-7610-4959-b9f6-bd033c979d8e",
"slug": "recursive",
"title": "Recursive",
"blurb": "Use recursion to work out whether each item should be added.",
"authors": [
"kahgoh"
]
},
{
"uuid": "c51300cb-94f8-4a33-8f64-56deebaa1a22",
"slug": "dynamic-programming",
"title": "Dynamic Programming",
"blurb": "Build up a table of maximum values.",
"authors": [
"kahgoh"
]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Dynamic Programming

```java
import java.util.List;

class Knapsack {

int maximumValue(int maxWeight, List<Item> items) {
int [][]maxValues = new int[maxWeight + 1][items.size() + 1];

for (int nItems = 0; nItems <= items.size(); nItems++) {
maxValues[0][nItems] = 0;
}

for (int itemIndex = 0; itemIndex < items.size(); itemIndex++) {
Item item = items.get(itemIndex);

for (int capacity = 0; capacity <= maxWeight; capacity++) {
if (capacity < item.weight) {
maxValues[capacity][itemIndex + 1] = maxValues[capacity][itemIndex];
} else {
int valueWith = maxValues[capacity - item.weight][itemIndex] + item.value;
int valueWithout = maxValues[capacity][itemIndex];
maxValues[capacity][itemIndex + 1] = Math.max(valueWith, valueWithout);
}
}
}

return maxValues[maxWeight][items.size()];
}
}
```

This approach works by building a table of maximum total values.
The table is represented by the 2D [array][array] `maxValues`.
The table's axes are the capacity (starting from 0 and going up to the `maxWeight`) and the number of items (starting from 0 and going up to length of the `items` list).
The number of items always count from the first item.
1 is added to the `maxWeight` and the number of items to allow space for 0 weight and 0 items.

The first [for loop][for-loop] fills table for when there are no items available.
In this case, the maximum value must be 0 because there are no items.

The next for loops fills the rest of the table.
The outer for loop iterates over each item.
The inner loop fills calculates and stores the maximum value for the item and capacity.
When storing, 1 is added to the `itemIndex` on the left hand side because the first item in the `maxValues` array represents no items.
If the knapsack doesn't have enough capacity for the item, then maximum value is same as the maximum value _without_ the item.
The maximum value without the item is obtained simply by looking up the value for the previous item and capacity in the table (i.e `maxValues[capacity][itemIndex]`).

Otherwise, the maximum value is the greater of the value with and without the item.
The maximum value _with_ (`valueWith`) the item is obtained by, first, looking up the maximum value _without_ the item and enough capacity for the item (i.e `capacity - item.weight`).
The item's value (`item.value`) is then added to get the maximum value for the get the maximum value _with_ the item.

After the table is completely filled, the maximum value is obtained from the table.

[array]: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/arrays.html
[for-loop]: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/for.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
for (int itemIndex = 0; itemIndex < items.size(); itemIndex++) {
for (int capacity = 0; capacity <= maxWeight; capacity++) {
int valueWith = maxValues[capacity - item.weight][itemIndex] + item.value;
int valueWithout = maxValues[capacity][itemIndex];
maxValues[capacity][itemIndex + 1] = Math.max(valueWith, valueWithout);
}
}
86 changes: 86 additions & 0 deletions exercises/practice/knapsack/.approaches/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Introduction

There are a couple of approaches to solve Knapsack.
You can recursively determine whether each item should be added to the knapsack.
Or, you can solve it iteratively using a dynamic programming approach.

## General guidance

The key to solving Knapsack is to determine whether each item should be added to the knapsack or not.
An item should be added only if:

1. There is enough capacity to add the item and:
2. It increases the total value.

## Approach: Recursive

```java
import java.util.List;

class Knapsack {

int maximumValue(int maxWeight, List<Item> items) {
if (items.isEmpty()) {
return 0;
}

List<Item> remainingItems = items.subList(1, items.size());
int valueWithout = maximumValue(maxWeight, remainingItems);

Item item = items.get(0);
if (item.weight > maxWeight) {
return valueWithout;
}

int valueWith = maximumValue(maxWeight - item.weight, remainingItems) + item.value;
return Math.max(valueWithout, valueWith);
}
}
```

For more information, check the [Recursive approach][approach-recursive].

## Approach: Dynamic programming

```java
import java.util.List;

class Knapsack {

int maximumValue(int maxWeight, List<Item> items) {
int [][]maxValues = new int[maxWeight + 1][items.size() + 1];

for (int nItems = 0; nItems <= items.size(); nItems++) {
maxValues[0][nItems] = 0;
}

for (int itemIndex = 0; itemIndex < items.size(); itemIndex++) {
Item item = items.get(itemIndex);

for (int capacity = 0; capacity <= maxWeight; capacity++) {
if (capacity < item.weight) {
maxValues[capacity][itemIndex + 1] = maxValues[capacity][itemIndex];
} else {
int valueWith = maxValues[capacity - item.weight][itemIndex] + item.value;
int valueWithout = maxValues[capacity][itemIndex];
maxValues[capacity][itemIndex + 1] = Math.max(valueWith, valueWithout);
}
}
}

return maxValues[maxWeight][items.size()];
}
}
```

For more information, check the [dynamic programming approach][approach-dynamic].

## Which approach to use?

The recursive approach is inefficient because it recalculates the maximum value for some item combinations a number of times.
The dynamic programming approach avoids this by storing them in an [array][array].
In addition, the dynamic programming approach is also an iterative approach and avoids overhead of making method calls.

[approach-recursive]: https://exercism.org/tracks/java/exercises/knapsack/approaches/recursive
[approach-dynamic]: https://exercism.org/tracks/java/exercises/knapsack/approaches/dynamic-programming
[array]: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/arrays.html
35 changes: 35 additions & 0 deletions exercises/practice/knapsack/.approaches/recursive/content.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Recursive

```java
import java.util.List;

class Knapsack {

int maximumValue(int maxWeight, List<Item> items) {
if (items.isEmpty()) {
return 0;
}

List<Item> remainingItems = items.subList(1, items.size());
int valueWithout = maximumValue(maxWeight, remainingItems);

Item item = items.get(0);
if (item.weight > maxWeight) {
return valueWithout;
}

int valueWith = maximumValue(maxWeight - item.weight, remainingItems) + item.value;
return Math.max(valueWithout, valueWith);
}
}
```

The approach uses recursion to find the maximum value with and without each item.
Each iteration works on the first item in the list (`items.get(0)`).
If the list is empty, the maximum value must be `0`.
Otherwise, a recursive call is made to work out the maximum value _without_ the first item (`maximumValue(maxWeight, remainingItems)`).

If the there is not enough capacity to add the current item, the maximum value is the same as the maximum value without the first item.
Otherwise, another recursive call is made to calculate the maximum value _with_ the first item.
Since the item is included, the recursive call is done with the item's weight subtracted from the maximum weight (`maxWeight - item.weight`) and its value (`item.value`) is added.
The maximum value is greater of these two values.
5 changes: 5 additions & 0 deletions exercises/practice/knapsack/.approaches/recursive/snippet.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
int maximumValue(int maxWeight, List<Item> items) {
int valueWithout = maximumValue(maxWeight, remainingItems);
int valueWith = maximumValue(maxWeight - item.weight, remainingItems) + item.value;
return Math.max(valueWithout, valueWith);
}

0 comments on commit b9ce42a

Please sign in to comment.