Solving the 0/1 Knapsack: From Brute-Force Recursion to Memoization and Dynamic Programming

Problem Overview: The 0/1 Knapsack

Given a maximum capacity (or time limit) W and N distinct items, where each item has a weight (or time cost) and a value, the objective is to maximize the total value of selected items without exceeding the given capacity. Each item can be chosen at most once.

Constraints

  • Maximum Capacity W: 1 ≤ W ≤ 1000
  • Number of Items N: 1 ≤ N ≤ 100
  • Weight and Value per item: 1 to 100

Approach 1: Brute-Force Recursion

The most intuitive method evaluates every possible combination of items. For each item, we make a binary choice: either skip it or include it. This generates a binary tree of decisions with a time complexity of O(2^N), which is highly inefficient and will result in a Time Limit Exceeded error for larger inputs.

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int maxCapacity, itemCount;
vector<int> itemWeights, itemValues;
int optimalValue = 0;

void exploreChoices(int idx, int capacityLeft, int accumulatedValue) {
    if (capacityLeft < 0) return;
    if (idx == itemCount) {
        optimalValue = max(optimalValue, accumulatedValue);
        return;
    }
    // Decision 1: Skip the current item
    exploreChoices(idx + 1, capacityLeft, accumulatedValue);
    // Decision 2: Include the current item
    exploreChoices(idx + 1, capacityLeft - itemWeights[idx], accumulatedValue + itemValues[idx]);
}

int main() {
    cin >> maxCapacity >> itemCount;
    itemWeights.resize(itemCount);
    itemValues.resize(itemCount);
    for (int i = 0; i < itemCount; ++i) {
        cin >> itemWeights[i] >> itemValues[i];
    }
    exploreChoices(0, maxCapacity, 0);
    cout << optimalValue << endl;
    return 0;
}

Approach 2: Memoized Search (Top-Down DP)

The naive recursive approach suffers from overlapping subproblems. The maximum achievable value for a given item index and remaining capacity is recalculated repeatedly. By storing the results of these subproblems in a cache, we can prune the recursion tree. This top-down approach reduces the time complexity to O(N * W).

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int maxCapacity, itemCount;
vector<int> itemWeights, itemValues;
vector<vector<int>> memoCache;

int searchWithMemo(int idx, int capacityLeft) {
    if (idx == itemCount) return 0;
    if (memoCache[idx][capacityLeft] != -1) return memoCache[idx][capacityLeft];
    
    // Value if we skip the current item
    int skipValue = searchWithMemo(idx + 1, capacityLeft);
    int pickValue = 0;
    
    // Value if we include the current item
    if (capacityLeft >= itemWeights[idx]) {
        pickValue = itemValues[idx] + searchWithMemo(idx + 1, capacityLeft - itemWeights[idx]);
    }
    
    return memoCache[idx][capacityLeft] = max(skipValue, pickValue);
}

int main() {
    cin >> maxCapacity >> itemCount;
    itemWeights.resize(itemCount);
    itemValues.resize(itemCount);
    memoCache.assign(itemCount + 1, vector<int>(maxCapacity + 1, -1));
    
    for (int i = 0; i < itemCount; ++i) {
        cin >> itemWeights[i] >> itemValues[i];
    }
    cout << searchWithMemo(0, maxCapacity) << endl;
    return 0;
}

Approach 3: Iterative Dynamic Programming (Bottom-Up)

We can eliminate recursion entire by iteratively filling a 2D array. Let dp[i][w] represent the maximum value obtainable considering the first i items with a capacity limit of w. The state transition logic is straightforward: if the current item's weight exceeds the current capacity, we must skip it; otherwise, we take the maximum of skipping or including it.

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    int maxCapacity, itemCount;
    cin >> maxCapacity >> itemCount;
    
    vector<int> itemWeights(itemCount), itemValues(itemCount);
    for (int i = 0; i < itemCount; ++i) {
        cin >> itemWeights[i] >> itemValues[i];
    }
    
    // dp[i][w] stores the max value using first i items with capacity w
    vector<vector<int>> dp(itemCount + 1, vector<int>(maxCapacity + 1, 0));
    
    for (int i = 1; i <= itemCount; ++i) {
        for (int w = 0; w <= maxCapacity; ++w) {
            // Inherit the value from not picking the i-th item
            dp[i][w] = dp[i - 1][w];
            // Check if we can pick the i-th item (Note: 0-indexed arrays)
            if (w >= itemWeights[i - 1]) {
                dp[i][w] = max(dp[i][w], dp[i - 1][w - itemWeights[i - 1]] + itemValues[i - 1]);
            }
        }
    }
    
    cout << dp[itemCount][maxCapacity] << endl;
    return 0;
}

Tags: 0/1 Knapsack Dynamic Programming Depth-First Search memoization C++

Posted on Mon, 18 May 2026 13:23:16 +0000 by VDarkAzN