Skip to content

Commit c972696

Browse files
authored
Update Dynamic-Programming.md
1 parent b82e903 commit c972696

File tree

1 file changed

+263
-0
lines changed

1 file changed

+263
-0
lines changed

Notes/Dynamic-Programming.md

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,266 @@ public int pascal( int n, int k )
135135
```
136136

137137
## Knapsack Problem
138+
139+
### Problem Statement
140+
141+
You have a knapsack that can hold up to a certain weight _W_, and you have _N_ items that you can pack for a trip; however, all of these items might not fit in your knapsack.
142+
143+
Each item you can pack has a weight _w_ and a value _v_.
144+
145+
Your goal is to pack some items into your knapsack such that the total weight of all of the items doesn't exceed the maximum weight limit _W_, and the sum of all the values of the items in your knapsack is _maximized_.
146+
147+
### Approaches
148+
149+
1. Calculate all subsets of items and see which subset has the highest value, but also stays under the knapsack's weight limit
150+
2. Create some ratio for each item _v_ / _w_ and take the items with the highest ratio until you can't pack anymore
151+
3. Use dynamic programming
152+
153+
### Solving Knapsack: Take I - Subset Solution
154+
155+
Let's calculate all subsets of the _N_ items that we have.
156+
157+
This means that there are 2<sup>_N_</sup> possible subsets that we will need to go through.
158+
159+
However, if _N_ is sufficiently large (larger than 25), this approach will be too slow
160+
161+
### Solving Knapsack: Take II - Ratio Solution
162+
163+
Imagine we have a knapsack with _W_ = 6 and are given the following set of items:
164+
_w_ = {2, 2, 2, 5}
165+
_v_ = {7, 7, 7, 20}
166+
167+
Let's find the _v_ / _w_ ratio of each item
168+
_v_ / _W_ = {3.5, 3.5, 3.5, 4}
169+
170+
Now that we have the ratios, let's take the items with the greatest ratio until we have no more space left in the knapsack.
171+
172+
We will take the item with _w_ = 5, _v_ = 20 first and put it into oiur knapsack.
173+
174+
We now have 1 weight left in our knapsack, so we cannot fit any of the other items in our knapsack, so we end up with a value of 20.
175+
176+
However, if we would've taken the other three items, we would've had no weight left and a value of 21 instead.
177+
178+
### Solving Knapsack: Take III - Dynamic Programming
179+
180+
There are two approaches to dynamic programming:
181+
- _Top-down_: define the overall problem first and what subproblems you will need to solve in order to get your answer
182+
- _Bottom-up_: start by solving subproblems and build up to the subproblems
183+
184+
We will look at how to solve the knapsack problem using both approaches
185+
186+
#### Bottom-Up Solution
187+
188+
Using the bottom-up approach, we need to first solve all of the possible subproblems, and then we can use those to solve our overall problem.
189+
190+
We will create a table that will hold the solutions to these subproblems, and keep building on it until we are done and have our answer.
191+
192+
Let's say we have a knapsack with capacity _W_ = 6 and the following weights and values for _N_ = 5 items:
193+
_w_ = {3, 2, 1, 4, 5}
194+
_v_ = {25, 20, 15, 40, 50}
195+
196+
Let's figure out for all capacities, _j_, less than _W_ = 6, what is the maximum value we can get if we only use the first _i_ items
197+
198+
`dp[ i ][ j ]` will be the solution for each subproblem
199+
200+
Let's create our table; each cell will represent `dp[ i ][ j ]`
201+
202+
| | | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
203+
|---|---|---|---|---|---|---|---|---|
204+
| | 0 | | | | | | | |
205+
| w = 3, v = 25 | 1 | | | | | | | |
206+
| w = 2, v = 20 | 2 | | | | | | | |
207+
| w = 1, v = 15 | 3 | | | | | | | |
208+
| w = 4, v = 40 | 4 | | | | | | | |
209+
| w = 5, v = 50 | 5 | | | | | | | |
210+
211+
If we can't use any of the items (_i_ = 0), the value at any capacity will be 0
212+
213+
| | | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
214+
|---|---|---|---|---|---|---|---|---|
215+
| | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
216+
| w = 3, v = 25 | 1 | | | | | | | |
217+
| w = 2, v = 20 | 2 | | | | | | | |
218+
| w = 1, v = 15 | 3 | | | | | | | |
219+
| w = 4, v = 40 | 4 | | | | | | | |
220+
| w = 5, v = 50 | 5 | | | | | | | |
221+
222+
For _i_ = 1, we can only take the first item in our set, so we want to take it whenever we have the capacity for it
223+
224+
| | | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
225+
|---|---|---|---|---|---|---|---|---|
226+
| | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
227+
| w = 3, v = 25 | 1 | 0 | 0 | 0 | 25 | 25 | 25 | 25 |
228+
| w = 2, v = 20 | 2 | | | | | | | |
229+
| w = 1, v = 15 | 3 | | | | | | | |
230+
| w = 4, v = 40 | 4 | | | | | | | |
231+
| w = 5, v = 50 | 5 | | | | | | | |
232+
233+
For _i_ = 2, we can now take the second item in our set in addition to the first item, but only when taking the item at a given capacity will give us greater value than not taking it.
234+
235+
For example, at capacity _j_ = 2, previously when we didn't have the item, we could get at most 0 value, but if we now take the item, we can get at most 20 value.
236+
237+
At capacity _j_ = 3, previously when we didn't have the item, we could get at most 25 value, but if we now take the item, we can get 20 value AND the maximum value we could get previously at capacity _j_ = 1, which is 0 (since we are looking at a capacity of _j_ = 3, and we took the second item, the capacity we have left is _j_ = 1, so we look at the maximum value we could get at this weight with all of the previous items). In this case, we do not want to take the item, because doing so will give us a lower value (20 < 25).
238+
239+
At capacity _j_ = 5, previously when we didn't have the item, we could get at most 25 value, but if we now take the item, we can get 20 value AND the maximum value we could get previously at capacity _j_ = 3, which is 25 (since we are looking at a capacity of _j_ = 5, and we took the second item, the capacity we have left is _j_ = 3, so we look at the maximum value we could get at this weight with all of the previous items). In this case, we do want to take the item, because doing so will give us more value (45 > 25).
240+
241+
| | | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
242+
|---|---|---|---|---|---|---|---|---|
243+
| | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
244+
| w = 3, v = 25 | 1 | 0 | 0 | 0 | 25 | 25 | 25 | 25 |
245+
| w = 2, v = 20 | 2 | 0 | 0 | 20 | 25 | 25 | 25 | 25 |
246+
| w = 1, v = 15 | 3 | | | | | | | |
247+
| w = 4, v = 40 | 4 | | | | | | | |
248+
| w = 5, v = 50 | 5 | | | | | | | |
249+
250+
For _i_ = 3, we repeat the same process that we did for _i_ = 2.
251+
252+
| | | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
253+
|---|---|---|---|---|---|---|---|---|
254+
| | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
255+
| w = 3, v = 25 | 1 | 0 | 0 | 0 | 25 | 25 | 25 | 25 |
256+
| w = 2, v = 20 | 2 | 0 | 0 | 20 | 25 | 25 | 25 | 25 |
257+
| w = 1, v = 15 | 3 | 0 | 15 | 20 | 35 | 40 | 45 | 60 |
258+
| w = 4, v = 40 | 4 | | | | | | | |
259+
| w = 5, v = 50 | 5 | | | | | | | |
260+
261+
For _i_ = 4, we repeat the same process as before
262+
263+
| | | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
264+
|---|---|---|---|---|---|---|---|---|
265+
| | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
266+
| w = 3, v = 25 | 1 | 0 | 0 | 0 | 25 | 25 | 25 | 25 |
267+
| w = 2, v = 20 | 2 | 0 | 0 | 20 | 25 | 25 | 25 | 25 |
268+
| w = 1, v = 15 | 3 | 0 | 15 | 20 | 35 | 40 | 45 | 60 |
269+
| w = 4, v = 40 | 4 | 0 | 15 | 20 | 35 | 40 | 55 | 60 |
270+
| w = 5, v = 50 | 5 | | | | | | | |
271+
272+
For _i_ = 5, we repeat the same process as before
273+
274+
| | | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
275+
|---|---|---|---|---|---|---|---|---|
276+
| | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
277+
| w = 3, v = 25 | 1 | 0 | 0 | 0 | 25 | 25 | 25 | 25 |
278+
| w = 2, v = 20 | 2 | 0 | 0 | 20 | 25 | 25 | 25 | 25 |
279+
| w = 1, v = 15 | 3 | 0 | 15 | 20 | 35 | 40 | 45 | 60 |
280+
| w = 4, v = 40 | 4 | 0 | 15 | 20 | 35 | 40 | 55 | 60 |
281+
| w = 5, v = 50 | 5 | 0 | 15 | 20 | 35 | 40 | 55 | 60 |
282+
283+
Now that the table is filled out, we can solve our original problem, where _i_ = 5 and _j_ = 6, by looking up the answer from the table
284+
285+
So our answer is: `dp[ 5 ][ 6 ]` = 60
286+
287+
### Top-Down Solution
288+
289+
To solve the problem using the top-down approach, we will try to come up with some recursive function that will give us the answer.
290+
291+
As we did with the bottom-up approach, we will go item by item and see if we want to take it or not.
292+
293+
We will also keep track of how much weight we have left in the knapsack as we go through the items (this will help us determine if we can take an item or not).
294+
295+
_Note_: when we're looking at an item, we only have two options: _take_ or _ignore_
296+
297+
This is why this problem is called the "_0/1 Knapsack Problem_"
298+
299+
```java
300+
public int solve( int i, int j )
301+
{
302+
// Determines the max value you can get when starting at the ith item with j remaining weight left to use
303+
}
304+
```
305+
306+
Let's try and figure out some base cases for this function.
307+
308+
What happens if _j_ == 0?
309+
- the max value we get by starting at _any_ index with _no weight left_ will be zero
310+
311+
What happens if _i_ == _N_?
312+
- the max value we get by starting at an index _outside of our item range_ will be zero
313+
314+
Let's look at every other case where _i_ != _N_ and _j_ > 0
315+
316+
What happens if the weight of the _i_<sup>_th_</sup> item is greater than the remaining weight, _j_?
317+
318+
If the weight of the item we are currently looking at is _greater_ than the amount of weight we have left, there's no way that we can take this item, so we _ignore_ it
319+
320+
```java
321+
solve( i + 1, j )
322+
```
323+
324+
The maximum value we get by starting at an index _where we can't take the item_ will be determined by the maximum value we get by starting at the _next_ item with the _same_ remaining weight to use.
325+
326+
What happens if the weight of the _i_<sup>_th_</sup> item is less than or equal to the remaining weight, _j_?
327+
328+
_Remember_, we only have two options: _take_ or _ignore_
329+
330+
We know what ignoring an item looks like:
331+
332+
```java
333+
solve( i + 1, j )
334+
```
335+
336+
But what does _taking_ an item look like?
337+
338+
Well, we know that the value of the remaining weight will be different
339+
- we took an item, so we need to subtract out it's weight from the remaining weight
340+
341+
We also know that since we _took_ the item, we can add its value to our result
342+
343+
Our resulting function call for _taking_ an item would be
344+
345+
```java
346+
value[ i ] + solve( i + 1, j - weight[ i ] )
347+
```
348+
349+
The maximum value we get by starting at an index _where we take the item_ can be determined by the sum of
350+
- the value of the item, and
351+
- the maximum value we get by starting at the _next_ item with the new remaining weight
352+
353+
However, we just saw two separate function calls where we have enough weight to take an item, so which one do we use?
354+
355+
```java
356+
int ignore = solve( i + 1, j );
357+
int take = value[ i ] + solve( i + 1, j - weight[ i ] );
358+
Math.max( ignore, take );
359+
```
360+
361+
We now know our solution handles every case for the Knapsack problem, giving us the optimal solution when we call `solve( 0, W )`, and it shows the problem has an _optimal substructure_ - that is, an optimal solution can be constructed from the optimal solutions of its subproblems.
362+
363+
If we solve any subproblem `solve( i, j )`, we are _guaranteed_ to get the optimal answer, and we can use these optimal subproblems to solve _other_ subproblems.
364+
365+
We know that the problem has optimal substructure, but does it have any _overlapping subproblems_?
366+
367+
Let's look at an example:
368+
_v_ = {100, 70, 50, 10}
369+
_w_ = {10, 4, 6, 12}
370+
_W_ = 12
371+
372+
Imagine that we take the first item and ignore the next two
373+
- we will be at `solve( i = 3, j = 2 )`
374+
375+
Imagine that we ignore the first item and take the next two
376+
- we will be at `solve( i = 3, j = 2 )`
377+
378+
We have now determined we have _overlapping subproblems_, so we can save the value of a specific _index_ / _remaining weight_ pair, so we don't have to recompute the solution to the subproblem each time
379+
380+
```java
381+
int[][] dp = new int[ N ][ W ];
382+
public int solve( int i, int j )
383+
{
384+
// If there's no more space or we run out of items, we can't get anymore value
385+
if ( j == 0 || n == N )
386+
return 0;
387+
// If we have already solved this subproblem, return the value
388+
if ( dp[ i ][ j ] != 0 )
389+
return dp[ i ][ j ];
390+
391+
// If the weight of the current item is greater than the weight we have left in the knapsack, ignore it
392+
if ( weight[ i ] > j )
393+
return solve( i + 1, j );
394+
395+
// For every other case, determine if ignoring or taking the current item will yield more value
396+
int ignore = solve( i + 1, j );
397+
int take = value[ i ] + solve( i + 1, j - weight[ i ] );
398+
return dp[ i ][ j ] = Math.max( ignore, take );
399+
}
400+
```

0 commit comments

Comments
 (0)