0 1 Knapsack Problem in DSA Rust - Time & Space Complexity
We want to understand how the time needed to solve the 0 1 Knapsack problem changes as we add more items or increase the knapsack capacity.
How does the work grow when the input size grows?
Analyze the time complexity of the following code snippet.
fn knapsack(weights: &[usize], values: &[usize], capacity: usize) -> usize {
let n = weights.len();
let mut dp = vec![vec![0; capacity + 1]; n + 1];
for i in 1..=n {
for w in 0..=capacity {
if weights[i - 1] <= w {
dp[i][w] = dp[i - 1][w].max(dp[i - 1][w - weights[i - 1]] + values[i - 1]);
} else {
dp[i][w] = dp[i - 1][w];
}
}
}
dp[n][capacity]
}
This code finds the best total value of items that fit into the knapsack without exceeding its capacity.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Nested loops over items and capacities.
- How many times: Outer loop runs n times (number of items), inner loop runs capacity + 1 times.
Each item causes the inner loop to run through all capacities from 0 up to the max capacity.
| Input Size (n) | Capacity (W) | Approx. Operations |
|---|---|---|
| 10 | 50 | About 10 * 51 = 510 |
| 100 | 1000 | About 100 * 1001 = 100,100 |
| 1000 | 10000 | About 1000 * 10001 = 10,001,000 |
Pattern observation: The work grows roughly by multiplying the number of items by the capacity size.
Time Complexity: O(n * W)
This means the time needed grows proportionally to the number of items times the knapsack capacity.
[X] Wrong: "The time grows only with the number of items, not the capacity."
[OK] Correct: The inner loop depends on capacity, so larger capacity means more work even if items stay the same.
Understanding this time complexity helps you explain how dynamic programming solves problems efficiently by breaking them into smaller parts.
"What if we used recursion with memoization instead of loops? How would the time complexity change?"
Practice
Solution
Step 1: Understand the problem goal
The 0 1 Knapsack Problem tries to select items to maximize total value without exceeding the weight capacity.Step 2: Clarify constraints
Each item can be chosen only once (0 or 1 times), so the goal is to maximize value under weight limit.Final Answer:
The total value of items without exceeding the weight limit -> Option CQuick Check:
Maximize value under weight limit = The total value of items without exceeding the weight limit [OK]
- Confusing weight maximization with value maximization
- Selecting items multiple times
- Ignoring the weight limit
Solution
Step 1: Identify correct 2D vector initialization
In Rust, a 2D vector is created with vec![vec![initial_value; inner_size]; outer_size].Step 2: Match dimensions for DP table
DP table size is (n+1) rows and (capacity+1) columns, so vec![vec![0; capacity + 1]; n + 1] is correct.Final Answer:
let mut dp = vec![vec![0; capacity + 1]; n + 1]; -> Option AQuick Check:
2D vector with correct sizes = let mut dp = vec![vec![0; capacity + 1]; n + 1]; [OK]
- Using fixed-size arrays incorrectly
- Swapping rows and columns sizes
- Not making vector mutable when updating
let weights = vec![1, 3];
let values = vec![10, 20];
let capacity = 3;
let n = weights.len();
let mut dp = vec![vec![0; capacity + 1]; n + 1];
for i in 1..=n {
for w in 0..=capacity {
if weights[i - 1] <= w {
dp[i][w] = dp[i - 1][w].max(dp[i - 1][w - weights[i - 1]] + values[i - 1]);
} else {
dp[i][w] = dp[i - 1][w];
}
}
}
println!("{}", dp[n][capacity]);Solution
Step 1: Trace DP table filling for each item and capacity
For i=1 (weight=1, value=10): dp[1][1..3] = 10. For i=2 (weight=3, value=20): dp[2][3] = max(dp[1][3], dp[1][0]+20) = max(10, 20) = 20; dp[2][1..2] = dp[1][1..2] = 10.Step 2: Check final dp[n][capacity]
At capacity=3, dp[2][3] = max(10, 20) = 20, but also consider if both items fit: weight sum=4 > 3, so only one item can be chosen. The best is item 2 with value 20 or item 1 with value 10. But wait, item 1 weight=1 and item 2 weight=3, capacity=3, so only one item fits. The max value is 20.Final Answer:
20 -> Option BQuick Check:
Max value with capacity 3 = 20 [OK]
- Assuming both items fit when total weight exceeds capacity
- Mixing up indices in dp table
- Ignoring max function in dp update
let weights = vec![2, 3];
let values = vec![40, 50];
let capacity = 5;
let n = weights.len();
let mut dp = vec![vec![0; capacity + 1]; n + 1];
for i in 1..=n {
for w in 1..=capacity {
if weights[i] <= w {
dp[i][w] = dp[i - 1][w].max(dp[i - 1][w - weights[i]] + values[i]);
} else {
dp[i][w] = dp[i - 1][w];
}
}
}
println!("{}", dp[n][capacity]);Solution
Step 1: Identify index out of bounds error
Rust vectors are zero-indexed, but code uses weights[i] and values[i] where i starts from 1, causing out-of-bounds access.Step 2: Fix indexing to zero-based
Change weights[i] and values[i] to weights[i - 1] and values[i - 1] to access correct elements.Final Answer:
Change weights[i] to weights[i - 1] and values[i] to values[i - 1] -> Option AQuick Check:
Correct zero-based indexing fixes error [OK]
- Using 1-based indexing on zero-based vectors
- Incorrect loop ranges causing panic
- Wrong dp initialization size
Solution
Step 1: Evaluate possible item combinations within capacity
Check items that fit in capacity 5: - Item 1 (weight 2, value 3) - Item 2 (weight 3, value 4) - Item 3 (weight 4, value 5) - Item 4 (weight 5, value 6) Possible pairs: - Items 1+2: weight 5, value 7 - Items 1+3: weight 6 (too heavy) - Items 2+3: weight 7 (too heavy) - Item 4 alone: weight 5, value 6Step 2: Choose combination with highest value under capacity
Best is items 1 and 2 with total value 7 and weight exactly 5.Final Answer:
7 -> Option DQuick Check:
Max value with capacity 5 = 7 [OK]
- Choosing single highest value item ignoring weight
- Adding items exceeding capacity
- Forgetting to check pairs or subsets
Ready to prove it?
Pass the test and this pattern moves unknown
