0 1 Knapsack Problem in DSA C - Time & Space Complexity
We want to understand how the time needed to solve the 0 1 Knapsack Problem changes as the number of items and the knapsack capacity grow.
How does the algorithm's work increase when we add more items or increase the capacity?
Analyze the time complexity of the following dynamic programming solution.
int max(int a, int b) {
return (a > b) ? a : b;
}
int knapsack(int W, int wt[], int val[], int n) {
int dp[n+1][W+1];
for (int i = 0; i <= n; i++) {
for (int w = 0; w <= W; w++) {
if (i == 0 || w == 0)
dp[i][w] = 0;
else if (wt[i-1] <= w)
dp[i][w] = max(val[i-1] + dp[i-1][w - wt[i-1]], dp[i-1][w]);
else
dp[i][w] = dp[i-1][w];
}
}
return dp[n][W];
}
This code fills a table to find the best value achievable with given items and capacity.
Look at the loops that repeat work:
- Primary operation: The nested loops over items (n) and capacity (W).
- How many times: The outer loop runs n+1 times, and the inner loop runs W+1 times for each outer loop.
As we increase the number of items or the capacity, the work grows by multiplying these two.
| Input Size (n, W) | Approx. Operations |
|---|---|
| 10 items, capacity 10 | ~100 operations |
| 100 items, capacity 100 | ~10,000 operations |
| 1000 items, capacity 1000 | ~1,000,000 operations |
Pattern observation: Doubling items and capacity roughly squares the work needed.
Time Complexity: O(n * W)
This means the time grows proportionally to the number of items times the capacity size.
[X] Wrong: "The time depends only on the number of items, not the capacity."
[OK] Correct: The capacity size directly affects how many subproblems we solve, so it impacts the total work.
Understanding this complexity helps you explain how dynamic programming solves problems efficiently by breaking them into smaller parts.
"What if we used a recursive solution without memoization? How would the time complexity change?"
Practice
Solution
0/1 Constraint
The '0/1' in the name literally means each item has two states: not taken (0) or fully taken (1). There is no state for 0.5 or any fraction.Why Greedy Fails
Because you cannot take fractions, the greedy 'highest value/weight ratio first' strategy fails - taking a high-ratio item may block a better combination of whole items.Why DP is Needed
DP explores all combinations of 0/1 choices, which is required when greedy fails due to the binary selection constraint.Final Answer:
Each item is binary - take it whole or skip it. → Option B
- Confusing 0/1 Knapsack with Fractional Knapsack
- Thinking DP table data type restricts selection
- Assuming greedy works for 0/1 Knapsack
Solution
Table Dimensions
The DP table is dp[n+1][W+1]. With n=3 items and W=5 capacity: dp[4][6].Why +1
Index 0 represents the base case: dp[0][w] = 0 (no items) and dp[i][0] = 0 (zero capacity). The +1 makes these base cases fit naturally without off-by-one adjustments.In C
int dp[4][6]; - 4 rows (0 to 3 items), 6 columns (capacity 0 to 5).Final Answer:
4 × 6 → Option D
- Using dp[n][W] and missing the base case row/column
- Adding +1 to only one dimension
- Confusing n items with n+1 rows
Solution
Can't Include Overweight Item
If weight[i] > w, item i cannot fit - we must skip it.Inherit from Previous Row
dp[i][w] = dp[i-1][w]: the best value with first i items equals the best with first i-1 items.Full Recurrence
if (weight[i] > w): dp[i][w] = dp[i-1][w]. else: dp[i][w] = max(dp[i-1][w], value[i] + dp[i-1][w-weight[i]])Final Answer:
dp[i-1][w] → Option B
- Setting dp[i][w]=0 instead of carrying forward the previous best
- Applying include formula when item doesn't fit
- Reading dp[i][w-1] instead of dp[i-1][w]
Solution
Time Complexity
Two nested loops: outer over n items, inner over W+1 capacities. Each cell O(1). Total: O(n×W).Space Complexity
Full table dp[n+1][W+1] → O(n×W) space.Note
Space can be reduced to O(W) with 1D rolling array, but standard 2D table is O(n×W).Final Answer:
Time O(n×W), Space O(n×W) → Option A
- Confusing standard O(n×W) space with optimized O(W)
- Claiming O(2^n) which applies only to naive recursion
- Mixing up time and space complexities
Solution
Problem With Left-to-Right
With a 1D array, processing left to right means dp[w-weight[i]] has already been updated in this pass - item i gets counted again at smaller capacities.Right-to-Left Prevents Reuse
Iterating from W down to weight[i] ensures dp[w-weight[i]] still holds the pre-item-i value. Each item counted at most once.0/1 vs Unbounded
Right-to-left = 0/1 Knapsack. Left-to-right = Unbounded Knapsack.Final Answer:
Prevents reusing item i in the same pass. → Option B
- Using left-to-right which implements Unbounded Knapsack accidentally
- Thinking loop direction is about array bounds not item reuse
- Not knowing right-to-left is the key 0/1 vs Unbounded distinction
Ready to prove it?
Pass the test and this pattern moves unknown
