Dynamic Programming Techniques for 0-1 and Unbounded Knapsack Problems

Core Implementation Strategy

Resolving knapsack variations follows a structured pattern involving state definition, initialization, and recurrence relation formulation. The primary distinction lies in whether an item can be selected multiple times or only once.

Phase 1: 0-1 Knapsack Variant

In this scenario, each item is available exact once per solution instance. The goal is to maximize profit within a given weight limit.

Implementation via 2D Table

This method tracks the maximum profit obtainable considering the first i items against a capacity limit j. A two-dimensional array dp[i][c] represents the optimal value using subset of items up to index i with capacity c.

#include <iostream>
#include <vector>
#include <algorithm>

class KnapSolver {
public:
    int computeMaxProfit(const std::vector<int>& weights,
                         const std::vector<int>& values,
                         int itemCount, int maxCap) {
        
        std::vector<std::vector<int>> table(itemCount, std::vector<int>(maxCap + 1, 0));
        
        // Initialize first row based on the first item
        for (int j = weights[0]; j <= maxCap; ++j) {
            table[0][j] = values[0];
        }

        // Fill table
        for (int i = 1; i < itemCount; ++i) {
            for (int j = 1; j <= maxCap; ++j) {
                if (weights[i] > j) {
                    table[i][j] = table[i - 1][j];
                } else {
                    table[i][j] = std::max(table[i - 1][j], 
                                           table[i - 1][j - weights[i]] + values[i]);
                }
            }
        }
        return table[itemCount - 1][maxCap];
    }
};

int main() {
    int n, m;
    if (std::cin >> n >> m) {
        std::vector<int> weights(n);
        std::vector<int> values(n);
        
        for (int i = 0; i < n; ++i) std::cin >> weights[i];
        for (int i = 0; i < n; ++i) std::cin >> values[i];

        KnapSolver solver;
        std::cout << solver.computeMaxProfit(weights, values, n, m) << std::endl;
    }
    return 0;
}
Optimization to 1D Rolling Array

By observing that the current row depends solely on the previous row, memory usage reduces to $O(V)$. To prevent overwriting values required for the current calculation, traverse the capacity dimension in descending order.

#include <iostream>
#include <vector>
#include <algorithm>

class OptimizedKnapSolver {
public:
    int calculateMaxValue(const std::vector<int>& weights,
                          const std::vector<int>& values,
                          int itemCount, int maxCap) {
        
        std::vector<int> cache(maxCap + 1, 0);

        for (int i = 0; i < itemCount; ++i) {
            for (int j = maxCap; j >= weights[i]; --j) {
                cache[j] = std::max(cache[j], cache[j - weights[i]] + values[i]);
            }
        }
        return cache[maxCap];
    }
};

int main() {
    int n, m;
    if (std::cin >> n >> m) {
        std::vector<int> weights(n);
        std::vector<int> values(n);
        
        for (int i = 0; i < n; ++i) std::cin >> weights[i];
        for (int i = 0; i < n; ++i) std::cin >> values[i];

        OptimizedKnapSolver solver;
        std::cout << solver.calculateMaxValue(weights, values, n, m) << std::endl;
    }
    return 0;
}

Phase 2: Complete Knapsack Variant

This scenario permits infinite instances of each item type, subject to total capacity constraints.

Implementation via 2D Table

Similar to the 0-1 case, but the trensition equation utilizes the current item row dp[i][j-weight] instead of the previous row dp[i-1][j-weight], allowing reuse of the current item in the same capacity step.

#include <iostream>
#include <vector>
#include <algorithm>

class CompleteKnapSolver {
public:
    int computeFullUnboundValue(const std::vector<int>& weights,
                                const std::vector<int>& values,
                                int itemCount, int maxCap) {
        
        std::vector<std::vector<int>> dp(itemCount, std::vector<int>(maxCap + 1, 0));
        
        // Initialization handles multiple copies for the first item
        for (int j = weights[0]; j <= maxCap; ++j) {
            dp[0][j] = dp[0][j - weights[0]] + values[0];
        }

        for (int i = 1; i < itemCount; ++i) {
            for (int j = 1; j <= maxCap; ++j) {
                if (weights[i] > j) {
                    dp[i][j] = dp[i - 1][j];
                } else {
                    dp[i][j] = std::max(dp[i - 1][j], 
                                        dp[i][j - weights[i]] + values[i]);
                }
            }
        }
        return dp[itemCount - 1][maxCap];
    }
};

int main() {
    int n, m;
    if (std::cin >> n >> m) {
        std::vector<int> weights(n);
        std::vector<int> values(n);
        
        for (int i = 0; i < n; ++i) {
            std::cin >> weights[i] >> values[i];
        }

        CompleteKnapSolver solver;
        std::cout << solver.computeFullUnboundValue(weights, values, n, m) << std::endl;
    }
    return 0;
}
Optimization to 1D Array

In the complete knapsack problem, since multiple copies of a item can fill the bag, we iterate through the capacity in ascending order. This allows dp[j-weight] to already reflect the inclusion of the current item before calculating for capacity j.

#include <iostream>
#include <vector>
#include <algorithm>

class OptimizedCompleteSolver {
public:
    int getOptimalUnboundValue(const std::vector<int>& weights,
                               const std::vector<int>& values,
                               int itemCount, int maxCap) {
        
        std::vector<int> dp(maxCap + 1, 0);

        for (int i = 0; i < itemCount; ++i) {
            for (int j = weights[i]; j <= maxCap; ++j) {
                dp[j] = std::max(dp[j], dp[j - weights[i]] + values[i]);
            }
        }
        return dp[maxCap];
    }
};

int main() {
    int n, m;
    if (std::cin >> n >> m) {
        std::vector<int> weights(n);
        std::vector<int> values(n);
        
        for (int i = 0; i < n; ++i) {
            std::cin >> weights[i] >> values[i];
        }

        OptimizedCompleteSolver solver;
        std::cout << solver.getOptimalUnboundValue(weights, values, n, m) << std::endl;
    }
    return 0;
}

Tags: C++ Dynamic Programming Knapsack Problem algorithm Space Optimization

Posted on Thu, 07 May 2026 11:23:34 +0000 by pradee