3647. Maximum Weight in Two Bags 🔒
Problem Description
You have an integer array weights
representing the weights of different items, and two integers w1
and w2
representing the maximum weight capacities of two bags.
Your task is to pack items into these two bags following these rules:
- Each item can be placed in at most one bag (or not placed at all)
- Bag 1 can hold items with a total weight of at most
w1
- Bag 2 can hold items with a total weight of at most
w2
The goal is to find the maximum total weight that can be packed into both bags combined.
For example, if you have items with weights [1, 2, 3]
and bags with capacities w1 = 3
and w2 = 4
, you could:
- Put item with weight 3 in bag 1 (total: 3)
- Put items with weights 1 and 2 in bag 2 (total: 3)
- This gives a maximum total weight of 6
The problem is essentially a variant of the knapsack problem where you have two knapsacks instead of one, and each item can only be used once across both bags.
Intuition
When we see this problem, we need to think about the decisions we make for each item. For every item, we have three choices:
- Don't take the item at all
- Put it in bag 1 (if it fits)
- Put it in bag 2 (if it fits)
This naturally leads us to think about dynamic programming, where we track the state of both bags as we consider items one by one.
The key insight is that we need to keep track of the remaining capacities of both bags simultaneously. This is different from the classic knapsack problem where we only track one capacity. We can think of our state as (remaining capacity of bag 1, remaining capacity of bag 2)
.
For each state f[j][k]
, we store the maximum weight we can achieve when bag 1 has capacity j
and bag 2 has capacity k
. When we consider adding a new item with weight x
:
- If we put it in bag 1, we transition from state
f[j-x][k]
tof[j][k]
(adding weightx
) - If we put it in bag 2, we transition from state
f[j][k-x]
tof[j][k]
(adding weightx
) - If we skip it, we keep the current best value at
f[j][k]
We process items one by one, updating our DP table. To avoid using the same item multiple times in one iteration, we traverse the capacities in reverse order (from high to low). This ensures that when we update f[j][k]
, we're using the values from the previous iteration, not the current one.
The final answer is f[w1][w2]
, which represents the maximum weight we can achieve when both bags are at their full capacities.
Solution Approach
We implement a 2D dynamic programming solution where f[j][k]
represents the maximum weight we can achieve with bag 1 having capacity j
and bag 2 having capacity k
.
Initialization:
We create a 2D array f
with dimensions (w1 + 1) × (w2 + 1)
, initialized with zeros. This represents that initially, no items are placed in either bag.
f = [[0] * (w2 + 1) for _ in range(w1 + 1)]
Processing Items:
For each item with weight x
in the weights
array, we update our DP table. We iterate through all possible capacities of both bags in reverse order:
for x in weights:
for j in range(w1, -1, -1):
for k in range(w2, -1, -1):
The reverse traversal is crucial - it ensures we don't use the same item multiple times. When we update f[j][k]
, we're using values from the previous iteration (before considering the current item).
State Transitions:
For each state (j, k)
, we have three options:
- Skip the item: Keep
f[j][k]
as is - Put item in bag 1: If
x <= j
, updatef[j][k] = max(f[j][k], f[j - x][k] + x)
- Put item in bag 2: If
x <= k
, updatef[j][k] = max(f[j][k], f[j][k - x] + x)
if x <= j:
f[j][k] = max(f[j][k], f[j - x][k] + x)
if x <= k:
f[j][k] = max(f[j][k], f[j][k - x] + x)
Space Optimization:
Notice that we're using a 2D array instead of 3D. This is because when processing item i
, we only need information from item i-1
. By traversing in reverse order, we can reuse the same 2D array, reducing space complexity from O(n × w1 × w2)
to O(w1 × w2)
.
Final Result:
After processing all items, f[w1][w2]
contains the maximum weight that can be packed into both bags.
The time complexity is O(n × w1 × w2)
where n
is the number of items, and the space complexity is O(w1 × w2)
.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach.
Given:
weights = [2, 3, 4]
w1 = 5
(bag 1 capacity)w2 = 3
(bag 2 capacity)
Step 1: Initialize DP Table
We create a 2D array f
of size (6 × 4)
initialized with zeros:
k=0 k=1 k=2 k=3 j=0 0 0 0 0 j=1 0 0 0 0 j=2 0 0 0 0 j=3 0 0 0 0 j=4 0 0 0 0 j=5 0 0 0 0
Step 2: Process First Item (weight = 2) We traverse in reverse order. For each valid position:
- At
f[5][3]
: Can put in bag 1 →f[5][3] = max(0, f[3][3] + 2) = 2
- At
f[5][3]
: Can put in bag 2 →f[5][3] = max(2, f[5][1] + 2) = 2
- At
f[2][3]
: Can put in bag 1 →f[2][3] = max(0, f[0][3] + 2) = 2
- Similar updates for other valid positions...
After processing item 1:
k=0 k=1 k=2 k=3 j=0 0 0 0 0 j=1 0 0 0 0 j=2 0 0 2 2 j=3 0 0 2 2 j=4 0 0 2 2 j=5 0 0 2 2
Step 3: Process Second Item (weight = 3) Key updates:
- At
f[5][3]
: Can put in bag 1 →f[5][3] = max(2, f[2][3] + 3) = 5
- At
f[5][3]
: Can put in bag 2 →f[5][3] = max(5, f[5][0] + 3) = 5
- At
f[3][3]
: Can put in bag 1 →f[3][3] = max(2, f[0][3] + 3) = 3
After processing item 2:
k=0 k=1 k=2 k=3 j=0 0 0 0 0 j=1 0 0 0 0 j=2 0 0 2 2 j=3 0 0 2 3 j=4 0 0 2 3 j=5 0 0 2 5
Step 4: Process Third Item (weight = 4) Key updates:
- At
f[5][3]
: Can put in bag 1 →f[5][3] = max(5, f[1][3] + 4) = 5
- At
f[4][3]
: Can put in bag 1 →f[4][3] = max(3, f[0][3] + 4) = 4
After processing item 3:
k=0 k=1 k=2 k=3 j=0 0 0 0 0 j=1 0 0 0 0 j=2 0 0 2 2 j=3 0 0 2 3 j=4 0 0 2 4 j=5 0 0 2 5
Final Result:
f[5][3] = 5
is our answer.
This means we can pack a maximum total weight of 5. One optimal solution is:
- Put item with weight 2 in bag 2 (uses 2 out of 3 capacity)
- Put item with weight 3 in bag 1 (uses 3 out of 5 capacity)
- Skip item with weight 4
- Total weight = 2 + 3 = 5
Solution Implementation
1class Solution:
2 def maxWeight(self, weights: List[int], w1: int, w2: int) -> int:
3 # Initialize DP table where dp[i][j] represents the maximum weight
4 # that can be achieved with knapsack 1 having capacity i and knapsack 2 having capacity j
5 dp = [[0] * (w2 + 1) for _ in range(w1 + 1)]
6
7 # Process each weight/item
8 for weight in weights:
9 # Iterate through all possible capacities in reverse order
10 # (reverse to avoid using the same item multiple times)
11 for capacity1 in range(w1, -1, -1):
12 for capacity2 in range(w2, -1, -1):
13 # Option 1: Try to put current item in knapsack 1
14 if weight <= capacity1:
15 dp[capacity1][capacity2] = max(
16 dp[capacity1][capacity2], # Don't take this item
17 dp[capacity1 - weight][capacity2] + weight # Take item in knapsack 1
18 )
19
20 # Option 2: Try to put current item in knapsack 2
21 if weight <= capacity2:
22 dp[capacity1][capacity2] = max(
23 dp[capacity1][capacity2], # Current best (including option 1 if applicable)
24 dp[capacity1][capacity2 - weight] + weight # Take item in knapsack 2
25 )
26
27 # Return the maximum weight achievable with both knapsacks at full capacity
28 return dp[w1][w2]
29
1class Solution {
2 /**
3 * Finds the maximum weight that can be achieved by placing items into two knapsacks.
4 * This is a variant of the 0/1 knapsack problem with two knapsacks.
5 *
6 * @param weights Array of item weights
7 * @param w1 Capacity of the first knapsack
8 * @param w2 Capacity of the second knapsack
9 * @return Maximum total weight that can be placed in both knapsacks
10 */
11 public int maxWeight(int[] weights, int w1, int w2) {
12 // dp[i][j] represents the maximum weight achievable using
13 // capacity i from knapsack 1 and capacity j from knapsack 2
14 int[][] dp = new int[w1 + 1][w2 + 1];
15
16 // Iterate through each item
17 for (int currentWeight : weights) {
18 // Traverse in reverse order to avoid using the same item multiple times
19 // This ensures each item is considered only once (0/1 knapsack property)
20 for (int capacity1 = w1; capacity1 >= 0; capacity1--) {
21 for (int capacity2 = w2; capacity2 >= 0; capacity2--) {
22 // Option 1: Try to place current item in knapsack 1
23 if (currentWeight <= capacity1) {
24 dp[capacity1][capacity2] = Math.max(
25 dp[capacity1][capacity2],
26 dp[capacity1 - currentWeight][capacity2] + currentWeight
27 );
28 }
29
30 // Option 2: Try to place current item in knapsack 2
31 if (currentWeight <= capacity2) {
32 dp[capacity1][capacity2] = Math.max(
33 dp[capacity1][capacity2],
34 dp[capacity1][capacity2 - currentWeight] + currentWeight
35 );
36 }
37 }
38 }
39 }
40
41 // Return the maximum weight using full capacity of both knapsacks
42 return dp[w1][w2];
43 }
44}
45
1class Solution {
2public:
3 int maxWeight(vector<int>& weights, int w1, int w2) {
4 // dp[i][j] represents the maximum weight that can be achieved
5 // using capacity i from the first knapsack and capacity j from the second knapsack
6 vector<vector<int>> dp(w1 + 1, vector<int>(w2 + 1, 0));
7
8 // Iterate through each weight/item
9 for (int weight : weights) {
10 // Traverse in reverse order to avoid using the same item multiple times
11 // This implements 0/1 knapsack constraint (each item can be used at most once)
12 for (int capacity1 = w1; capacity1 >= 0; --capacity1) {
13 for (int capacity2 = w2; capacity2 >= 0; --capacity2) {
14 // Option 1: Try to put current item in the first knapsack
15 if (weight <= capacity1) {
16 dp[capacity1][capacity2] = max(
17 dp[capacity1][capacity2],
18 dp[capacity1 - weight][capacity2] + weight
19 );
20 }
21
22 // Option 2: Try to put current item in the second knapsack
23 if (weight <= capacity2) {
24 dp[capacity1][capacity2] = max(
25 dp[capacity1][capacity2],
26 dp[capacity1][capacity2 - weight] + weight
27 );
28 }
29 }
30 }
31 }
32
33 // Return the maximum weight achievable using both knapsacks
34 return dp[w1][w2];
35 }
36};
37
1/**
2 * Calculates the maximum weight that can be achieved using items with given weights
3 * and two knapsacks with capacities w1 and w2.
4 * Each item can be placed in either knapsack or not used at all.
5 *
6 * @param weights - Array of item weights
7 * @param w1 - Capacity of the first knapsack
8 * @param w2 - Capacity of the second knapsack
9 * @returns Maximum total weight that can be achieved
10 */
11function maxWeight(weights: number[], w1: number, w2: number): number {
12 // dp[i][j] represents the maximum weight achievable with
13 // first knapsack capacity i and second knapsack capacity j
14 const dp: number[][] = Array.from(
15 { length: w1 + 1 },
16 () => Array(w2 + 1).fill(0)
17 );
18
19 // Process each item
20 for (const weight of weights) {
21 // Traverse in reverse order to avoid using the same item multiple times
22 for (let capacity1 = w1; capacity1 >= 0; capacity1--) {
23 for (let capacity2 = w2; capacity2 >= 0; capacity2--) {
24 // Try placing current item in the first knapsack
25 if (weight <= capacity1) {
26 dp[capacity1][capacity2] = Math.max(
27 dp[capacity1][capacity2],
28 dp[capacity1 - weight][capacity2] + weight
29 );
30 }
31
32 // Try placing current item in the second knapsack
33 if (weight <= capacity2) {
34 dp[capacity1][capacity2] = Math.max(
35 dp[capacity1][capacity2],
36 dp[capacity1][capacity2 - weight] + weight
37 );
38 }
39 }
40 }
41 }
42
43 // Return the maximum weight achievable with both knapsacks at full capacity
44 return dp[w1][w2];
45}
46
Time and Space Complexity
Time Complexity: O(n × w1 × w2)
The algorithm iterates through each weight in the weights
array (n elements), and for each weight, it performs nested iterations through all possible capacities from w1
to 0
and w2
to 0
. This results in n × (w1 + 1) × (w2 + 1)
operations, which simplifies to O(n × w1 × w2)
.
Space Complexity: O(w1 × w2)
The algorithm uses a 2D array f
with dimensions (w1 + 1) × (w2 + 1)
to store the dynamic programming states. No other data structures scale with the input size, so the space complexity is O(w1 × w2)
.
Common Pitfalls
1. Incorrect Traversal Order - Using Forward Iteration
One of the most common mistakes is iterating through capacities in forward order instead of reverse order. This leads to using the same item multiple times.
Incorrect Code:
# WRONG - Forward iteration allows reusing items
for weight in weights:
for capacity1 in range(w1 + 1): # Forward order
for capacity2 in range(w2 + 1): # Forward order
if weight <= capacity1:
dp[capacity1][capacity2] = max(
dp[capacity1][capacity2],
dp[capacity1 - weight][capacity2] + weight
)
if weight <= capacity2:
dp[capacity1][capacity2] = max(
dp[capacity1][capacity2],
dp[capacity1][capacity2 - weight] + weight
)
Why it's wrong: When we update dp[capacity1][capacity2]
, we might use dp[capacity1 - weight][capacity2]
which was already updated in the current iteration (considering the same item). This effectively allows using the same item multiple times.
Solution: Always use reverse iteration when dealing with 0/1 knapsack variants:
for capacity1 in range(w1, -1, -1): # Reverse order
for capacity2 in range(w2, -1, -1): # Reverse order
2. Updating Both Bags in Separate If-Statements Without Proper Max Comparison
Another subtle issue occurs when the two if-statements for bag updates aren't properly structured, potentially overwriting a better solution.
Problematic Pattern:
# Potentially problematic if not careful with max comparisons if weight <= capacity1: dp[capacity1][capacity2] = dp[capacity1 - weight][capacity2] + weight # Missing max() if weight <= capacity2: dp[capacity1][capacity2] = dp[capacity1][capacity2 - weight] + weight # Overwrites!
Solution: Always use max()
to preserve the best solution found so far:
if weight <= capacity1:
dp[capacity1][capacity2] = max(
dp[capacity1][capacity2],
dp[capacity1 - weight][capacity2] + weight
)
if weight <= capacity2:
dp[capacity1][capacity2] = max(
dp[capacity1][capacity2], # Preserves previous best
dp[capacity1][capacity2 - weight] + weight
)
3. Using 3D DP Array When 2D Suffices
While not incorrect, using a 3D array dp[i][j][k]
where i
represents items considered is unnecessary and wastes memory.
Space-Inefficient Approach:
# Unnecessary 3D array
dp = [[[0] * (w2 + 1) for _ in range(w1 + 1)] for _ in range(len(weights) + 1)]
Solution: Since we only need the previous state when processing each item, a 2D array with reverse iteration is sufficient, reducing space complexity from O(n × w1 × w2) to O(w1 × w2).
How many times is a tree node visited in a depth first search?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!