In standard textbook explanations of the 0/1 Knapsack problem, the solution uses a 2D table dp[n][W] where each cell represents the max value using a subset of items under capacity W.
Then instructors show an optimization: ‘Just replace the 2D array with a 1D array of size W, but you MUST iterate backwards from W down to weight[i].’ If you iterate forward, the whole thing breaks. Why does iterating backwards prevent duplicate item selection, while forward iteration works for Coin Change (Unbounded Knapsack)?
Here is the modern C++20 implementation of the 0/1 Knapsack with 1D Backward Sweep. By reserving contiguous memory and sweeping from
capacitydown toweight[i], we eliminate all intermediate row allocations.Complexity:
O(N * W)time, but auxiliary space drops fromO(N * W)to justO(W).This is one of the most fundamental ‘aha!’ moments in dynamic programming. Let’s walk through the memory mechanics so you never forget it.
1. The 2D State Transition
In the classic 0/1 Knapsack, the formula is:
Notice the critical detail: in Option B,
dp[i-1][w - weight[i]]comes from rowi-1(before itemiwas even considered). That is what guarantees you only take itemiat most once.2. Compressing to a 1D Array
Notice that to compute row
i, you only ever look at rowi-1. You don’t need rowsi-2,i-3, etc. So we can just reuse a single 1D array:dp[w].What happens if you iterate FORWARD (w = weight[i] to W)?
Suppose item 1 has
weight = 2, value = 10and capacity is6.w = 2:dp[2] = dp[0] + 10 = 10.w = 4:dp[4] = dp[4 - 2] + 10 = dp[2] + 10 = 10 + 10 = 20! (Wait, you just reused item 1 twice!)w = 6:dp[6] = dp[4] + 10 = 30! (You used item 1 three times!)Because you updated smaller weights first, larger weights read the already updated values from the current item. That turns it into Unbounded Knapsack (infinite items)!
What happens if you iterate BACKWARD (w = W down to weight[i])?
w = 6: readsdp[4](which is still 0 from the previous item!).dp[6] = 0 + 10 = 10.w = 4: readsdp[2](which is still 0!).dp[4] = 0 + 10 = 10.w = 2: readsdp[0](which is 0!).dp[2] = 0 + 10 = 10.By sweeping backwards, whenever you query
w - weight[i], that smaller index has not yet been touched for the current item. It still holds the pristine value from itemi-1!Production Python 3.12 Implementation
Summary Rule of Thumb
W → weight).weight → W).