0/1 Knapsack Pattern

Problems following 0/1 Knapsack Pattern

1. 0/1 Knapsack

Problem:

Given the weights w and profits p of ‘N’ items, need to find a subset of these items which will give max profit with contstraint that their cumulative sum should not be greater than total knapsack capacity C.

Each item can only be selected only once and each item will be either taken (1) or skipped (0).

Example:
Items: {Apple, Orange, Banana, Melon}
Weights(w): {2, 3, 1, 4}
Profits(p): {4, 5, 3, 7}
Knapsack Capacity(C): 5

Answer: 10 - Banana + Melon with total weight 5
All other combinations with weight 5 or less has lesser profits.


Brute-Force : Recursive Solution

Code :

def solve_knapsack_recursion(profits, weights, capacity):
    item_index = 0
    return solve_knapsack_recursion_util(profits, weights, capacity, item_index)

def solve_knapsack_recursion_util(profits, weights, capacity, item_index):
    if (capacity <= 0 or item_index >= len(profits)):
        return 0

    profit1 = 0
    if (weights[item_index] <= capacity):  # can be included
        profit1 = profits[item_index] + solve_knapsack_recursion_util(profits, weights, 
        capacity-weights[item_index], item_index+1)
    
    profit2 = solve_knapsack_recursion_util(profits, weights, capacity, item_index+1)

    return max(profit1, profit2)


print("Recursive Method :")
print(solve_knapsack_recursion([1, 6, 10, 16], [1, 2, 3, 5], 7))
print(solve_knapsack_recursion([1, 6, 10, 16], [1, 2, 3, 5], 6))

Output:

Recursive Method :
22
17

Complexity:



Identifying the problem as DP:


DP : Recursion + Memoization (Top-Down) Solution

Code:

def solve_knapsack_dp_memoization(profits, weights, capacity):
    memory = [[None]*(capacity+1) for i in range(len(profits))]
    item_index = 0
    return solve_knapsack_dp_memoization_util(profits, weights, capacity, item_index, memory)

def solve_knapsack_dp_memoization_util(profits, weights, capacity, item_index, memory):
    if (capacity <= 0 or item_index >= len(profits)):
        return 0
    
    if(memory[item_index][capacity]):
        return memory[item_index][capacity]
    
    profit1 = 0
    if (weights[item_index] <= capacity):  # can be included
        profit1 = profits[item_index] + solve_knapsack_dp_memoization_util(profits, weights, 
        capacity-weights[item_index], item_index+1, memory)

    profit2 = solve_knapsack_dp_memoization_util(profits, weights, capacity, item_index+1, memory)
    memory[item_index][capacity] = max(profit1, profit2)

    return memory[item_index][capacity]


print("\nDP -> Recursion + Memoization Method :")
print(solve_knapsack_dp_memoization([1, 6, 10, 16], [1, 2, 3, 5], 7))
print(solve_knapsack_dp_memoization([1, 6, 10, 16], [1, 2, 3, 5], 6))

Output:

DP -> Recursion + Memoization Method :
22
17

Complexity:


DP : Iteration + Tabulation (Bottom-Up) Solution

Table Filling Formula:

table[i][c] = max(table[i-1][c], profits[i] + table[i-1][c-weights[i])

Tabulation:

Final Table:

Code:

def solve_knapsack_dp_tabulation(profits, weights, capacity):
    table = [[0]*(capacity+1) for i in range(len(profits))]

    # When capacity is zero then, none of the item can be selected
    for i in range(len(profits)):
        table[i][0] = 0
    
    # If we consider only first weight, we will take it if its weight is less than capacity
    for c in range(capacity+1):
        if(weights[0] <= c):
            table[0][c] = profits[0]
    
    # Now consider rest by taking max of 1) current item can be included 2) current item is excluded
    for i in range(1, len(profits)):
        for c in range(1, capacity+1):
            if(weights[i] <= c):
                table[i][c] = max(table[i-1][c], profits[i] + table[i-1][c-weights[i]])
            else:
                table[i][c] = table[i-1][c]
    
    return table[i][c]


print("\nDP -> Iteration + Tabulation Method :")
print(solve_knapsack_dp_tabulation([1, 6, 10, 16], [1, 2, 3, 5], 7))
print(solve_knapsack_dp_tabulation([1, 6, 10, 16], [1, 2, 3, 5], 6))

Output:

DP -> Iteration + Tabulation Method :
22
17

Complexity:

Finding Selected Items:

Code:

def solve_knapsack_dp_tabulation(profits, weights, capacity):
    table = [[-1]*(capacity+1) for _ in range(len(profits))]

    # When capacity is zero then, none of the item can be selected
    for i in range(len(profits)):
        table[i][0] = 0

    # If we consider only first weight, we will take it if its weight is less than capacity
    for c in range(capacity+1):
        if(weights[0] <= c):
            table[0][c] = profits[0]

    # Now consider rest by taking max of 1) current item can be included 2) current item is excluded
    for i in range(1, len(profits)):
        for c in range(1, capacity+1):
            if(weights[i] <= c):
                table[i][c] = max(table[i-1][c], profits[i] + table[i-1][c-weights[i]])
            else:
                table[i][c] = table[i-1][c]

    pick_selected_items(table, weights, profits)

    return table[i][c]


def pick_selected_items(table, weights, profits):
    i, c = len(table) - 1, len(table[0]) - 1
    total_profit = table[i][c]

    print("Selected items are : ")

    while(total_profit > 0 and i-1 >= 0):
        if(table[i][c] != table[i-1][c]):
            print(f"item - {i+1} with weight : {weights[i]} and total_profit : {profits[i]}")
            total_profit -= profits[i]
            c -= weights[i]
        i -= 1

    if total_profit != 0:
        print(f"item - {1} with weight : {weights[0]} and total_profit : {profits[0]}")


print("\nDP -> Iteration + Tabulation Method :")
print(solve_knapsack_dp_tabulation([1, 6, 10, 16], [1, 2, 3, 5], 7))
print(solve_knapsack_dp_tabulation([1, 6, 10, 16], [1, 2, 3, 5], 6))

Output:

DP -> Iteration + Tabulation Method :
Selected items are : 
item - 4 with weight : 5 and total_profit : 16
item - 2 with weight : 2 and total_profit : 6
22
Selected items are : 
item - 3 with weight : 3 and total_profit : 10
item - 2 with weight : 2 and total_profit : 6
item - 1 with weight : 1 and total_profit : 1
17


Space Optimized Solution : Solving in O(C) space

Code:

def solve_knapsack_dp_tabulation_optimized_space(profits, weights, capacity):
    table = [0]*(capacity+1)
    
    # If we consider only first weight, we will take it if its weight is less than capacity
    for c in range(capacity+1):
        if(weights[0] <= c):
            table[c] = profits[0]
    
    # Now consider rest by taking max of 1) current item can be included 2) current item is excluded
    for i in range(1, len(profits)):
        for c in range(capacity, -1, -1):
            if(weights[i] <= c):
                table[c] = max(table[c], profits[i] + table[c-weights[i]])
            else:
                table[c] = table[c]
    
    return table[capacity]


print("\nDP -> Iteration + Tabulation - Optimized Space Method :")
print(solve_knapsack_dp_tabulation_optimized_space([1, 6, 10, 16], [1, 2, 3, 5], 7))
print(solve_knapsack_dp_tabulation_optimized_space([1, 6, 10, 16], [1, 2, 3, 5], 6))

Output:

DP -> Iteration + Tabulation - Optimized Space Method :
22
17

Complexity:



2. Equal Subset Sum Partition

Problem:



3. Subset Sum

Problem:



4. Minimum Subset Sum Difference

Problem:



5. Count of Subset Sum

Problem:



6. Target Sum

Problem:




← Previous: Fibonacci Numbers Pattern

Next: Unbounded Knapsack Pattern →