# 0 - 1 Knapsack Problem

Let's discuss one of my favorite DP problem.

Given weights and values of n items, put these items in a knapsack of capacity W to get the maximum total value in the knapsack. In other words, given two integer arrays val[0..n-1] and wt[0..n-1] which represent values and weights associated with n items respectively. Also given an integer W which represents knapsack capacity, find out the maximum value subset of val[] such that sum of the weights of this subset is smaller than or equal to W. You cannot break an item, either pick the complete item, or don’t pick it (0-1 property).

### Recursive Solution

#### Idea

Let's see if we can find the subproblem so that this problem can be solved recursively.

- Looking at the last item; it could be included or excluded in the optimal solution.
- Taking out item one by one until we run out capacity
- When the capacity is zero or knapsack is empty, the max value is 0

#### Implementation

Code the rules listed above in Python

```
'''
@W:int -> capacity
@wt:int[] -> array of item's weights
@val:int[] -> array of item's values
@n:int[] -> number of items
'''
def knapSackRec(W, wt, val, n):
if W == 0 or n == 0:
return 0
lastWeight = wt[n - 1]
lastValue = val[n - 1]
if (W - lastWeight < 0):
return knapSackRec(W, wt, val, n - 1) # last item will not be included
return max(
lastValue + knapSackRec(W - lastWeight, wt, val, n - 1),
knapSackRec(W, wt, val, n - 1)
)
```

#### Complexity

To analyze the time complexity, let's begin with very simple case.

Assuming **W = 2**, **n = 3**, **wi = [1, 1, 1]**. Then we can expand subproblems below:

```
K(3, 2) ---------> K(n, W)
/ \
/ \
K(2,2) K(2,1)
/ \ / \
/ \ / \
K(1,2) K(1,1) K(1,1) K(1,0)
/ \ / \ / \
/ \ / \ / \
K(0,2) K(0,1) K(0,1) K(0,0) K(0,1) K(0,0)
```

Time: O(2 ^ n)

### DP Solution

#### Idea

Speaking of algorithm part, we will use the idea inspired by recursive solution.

Meanwhile, we need to cache the solution for each subproblem.

#### Implementation

```
'''
@W:int -> capacity
@wt:int[] -> array of item's weights
@val:int[] -> array of item's values
@n:int[] -> number of items
'''
def knapSackDP(W, wt, val, n):
cache = [[0] * (W + 1) for x in range(n + 1)]
for i in range(n + 1):
for wi in range(W + 1):
if i == 0 or wi == 0:
cache[i][wi] = 0
break
lastWeight = wt[i - 1]
lastValue = val[i - 1]
if (wi - lastWeight) < 0:
cache[i][wi] = cache[i - 1][wi]
else:
cache[i][wi] = max(
lastValue + cache[i - 1][wi - lastWeight],
cache[i - 1][wi]
)
return cache[n][W]
```

#### Complexity

Space: O(n * W)

Time: O(n * W)