-
-
Notifications
You must be signed in to change notification settings - Fork 679
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add approaches for Knapsack * Fix formatting in introduction.md
- Loading branch information
Showing
6 changed files
with
217 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
] | ||
} | ||
] | ||
} |
57 changes: 57 additions & 0 deletions
57
exercises/practice/knapsack/.approaches/dynamic-programming/content.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
7 changes: 7 additions & 0 deletions
7
exercises/practice/knapsack/.approaches/dynamic-programming/snippet.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
35
exercises/practice/knapsack/.approaches/recursive/content.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
5
exercises/practice/knapsack/.approaches/recursive/snippet.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |