2548. Maximum Price to Fill a Bag π
Problem Description
You have a collection of items, each with a specific price and weight. Your goal is to fill a bag that has a limited capacity to maximize the total value.
Given:
- A 2D array
items
whereitems[i] = [price_i, weight_i]
represents the price and weight of the i-th item - A positive integer
capacity
representing the maximum weight the bag can hold
Key rules:
- Each item can be divided into smaller portions (fractional parts are allowed)
- When you take a fraction of an item, both its weight and price are scaled proportionally
- For example, taking half of an item means taking half its weight and getting half its price
- You need to maximize the total price while ensuring the total weight doesn't exceed the bag's capacity
The problem asks you to return the maximum total price you can achieve by filling the bag. If it's impossible to completely fill the bag to its exact capacity, return -1
.
This is essentially the fractional knapsack problem where items can be broken into smaller pieces. The greedy approach works here: prioritize items with the highest value-to-weight ratio (unit price), taking as much of each item as possible until the bag reaches its capacity limit.
Example: If you have an item with price 10 and weight 5, its unit price is 10/5 = 2
. If you can only fit weight 3 in your bag, you'd take 3/5
of the item, getting a price of 10 * (3/5) = 6
.
Intuition
When we can break items into smaller pieces, the strategy becomes clear: we want to get the most value for every unit of weight we put in the bag. Think of it like shopping with a limited budget - you'd want to buy items that give you the best "bang for your buck."
The key insight is to calculate the unit price (value per weight) for each item: price/weight
. This tells us how much value we get for each unit of weight. An item with price 10 and weight 2 has a unit price of 10/2 = 5
, meaning we get 5 units of value for every 1 unit of weight.
Since we can take fractional parts of items, we should greedily take items with the highest unit price first. This is different from the classic 0/1 knapsack problem where items can't be divided - there, greedy doesn't always work because you might need to skip a high-value heavy item for multiple lighter items.
Here's the thought process:
- Sort all items by their unit price in descending order
- Take items one by one, starting with the highest unit price
- For each item, take as much as possible (either the whole item or just enough to fill the remaining capacity)
- Continue until the bag is exactly full
The special constraint here is that we must fill the bag exactly to its capacity. If after taking all available items we still have unused capacity, it means it's impossible to exactly fill the bag, so we return -1
.
For example, if the bag capacity is 10 and we only have items with total weight 8, we can't exactly fill it to 10, so the answer would be -1
. This is why the solution checks if capacity
becomes exactly 0 at the end.
Solution Approach
The implementation follows a greedy strategy with sorting to maximize the total price.
Step 1: Sort items by unit price
sorted(items, key=lambda x: x[1] / x[0])
We sort the items by their weight-to-price ratio weight/price
in ascending order. This is equivalent to sorting by unit price price/weight
in descending order. Items with lower weight/price
ratios have higher unit prices and should be selected first.
Step 2: Greedily select items
We iterate through the sorted items and for each item [p, w]
:
v = min(w, capacity)
Calculate how much of the current item we can take. It's either:
- The entire item weight
w
if we have enough capacity - The remaining
capacity
if the item is heavier than what we can carry
Step 3: Update total price and remaining capacity
ans += v / w * p capacity -= v
- Add the proportional price to our answer:
(v/w) * p
wherev/w
is the fraction of the item taken - Reduce the remaining capacity by the weight we just added
Step 4: Check if bag is exactly full
return -1 if capacity else ans
After processing all items:
- If
capacity == 0
, the bag is exactly full, return the total priceans
- If
capacity > 0
, we couldn't fill the bag completely, return-1
Time Complexity: O(n log n)
where n is the number of items, dominated by the sorting step.
Space Complexity: O(1)
if we don't count the space used for sorting (typically O(log n)
for the sorting algorithm's recursion stack).
The algorithm guarantees the optimal solution because at each step, we're maximizing the value gained per unit of weight used, which is the optimal strategy when items can be fractionally divided.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's work through a concrete example to illustrate the solution approach.
Given:
items = [[60, 10], [100, 20], [120, 30]]
(format: [price, weight])capacity = 50
Step 1: Calculate unit prices and sort
First, let's calculate the unit price (price/weight) for each item:
- Item 0: price=60, weight=10 β unit price = 60/10 = 6.0
- Item 1: price=100, weight=20 β unit price = 100/20 = 5.0
- Item 2: price=120, weight=30 β unit price = 120/30 = 4.0
Sort items by unit price (descending):
- Items after sorting:
[[60, 10], [100, 20], [120, 30]]
(already in the right order)
Step 2: Greedily fill the bag
Starting with capacity = 50
and total_price = 0
:
Item 1 [60, 10]:
- Can we take all 10 units? Yes, because capacity (50) β₯ weight (10)
- Take entire item:
v = min(10, 50) = 10
- Add to total:
total_price = 0 + (10/10) Γ 60 = 60
- Update capacity:
capacity = 50 - 10 = 40
Item 2 [100, 20]:
- Can we take all 20 units? Yes, because capacity (40) β₯ weight (20)
- Take entire item:
v = min(20, 40) = 20
- Add to total:
total_price = 60 + (20/20) Γ 100 = 160
- Update capacity:
capacity = 40 - 20 = 20
Item 3 [120, 30]:
- Can we take all 30 units? No, because capacity (20) < weight (30)
- Take partial item:
v = min(30, 20) = 20
- Fraction taken: 20/30 = 2/3
- Add to total:
total_price = 160 + (20/30) Γ 120 = 160 + 80 = 240
- Update capacity:
capacity = 20 - 20 = 0
Step 3: Check if bag is exactly full
Final capacity = 0, so the bag is exactly full.
Return total_price = 240
The optimal solution takes all of items 1 and 2, plus 2/3 of item 3, achieving a maximum total price of 240.
Solution Implementation
1from typing import List
2
3class Solution:
4 def maxPrice(self, items: List[List[int]], capacity: int) -> float:
5 # Initialize the maximum price/value we can obtain
6 max_value = 0.0
7
8 # Sort items by value-to-weight ratio (price per unit weight) in descending order
9 # Each item is [price, weight], so ratio is price/weight
10 # We want higher ratios first, so we sort in reverse
11 sorted_items = sorted(items, key=lambda item: item[0] / item[1], reverse=True)
12
13 # Greedily select items or fractions of items
14 for price, weight in sorted_items:
15 # Take as much of the current item as possible
16 # Either take the whole item or fill remaining capacity
17 amount_to_take = min(weight, capacity)
18
19 # Add the proportional value to our total
20 # If we take a fraction of the item, we get a fraction of its price
21 max_value += (amount_to_take / weight) * price
22
23 # Reduce the remaining capacity
24 capacity -= amount_to_take
25
26 # If capacity is exhausted, we can stop early
27 if capacity == 0:
28 break
29
30 # Return the maximum value obtained
31 # Original code returns -1 if capacity remains, but typically we return max_value
32 return max_value if capacity == 0 else -1
33
1class Solution {
2 public double maxPrice(int[][] items, int capacity) {
3 // Sort items by value-to-weight ratio in descending order
4 // Compare (price1/weight1) vs (price2/weight2) using cross multiplication
5 // to avoid floating point division: price1 * weight2 vs price2 * weight1
6 Arrays.sort(items, (itemA, itemB) -> itemB[0] * itemA[1] - itemA[0] * itemB[1]);
7
8 double totalValue = 0.0;
9
10 // Greedily select items with highest value-to-weight ratio first
11 for (int[] item : items) {
12 int price = item[0];
13 int weight = item[1];
14
15 // Take as much of this item as possible (fractional knapsack)
16 int weightToTake = Math.min(weight, capacity);
17
18 // Add proportional value to total
19 totalValue += (double) weightToTake / weight * price;
20
21 // Reduce remaining capacity
22 capacity -= weightToTake;
23 }
24
25 // Return -1 if capacity cannot be fully utilized, otherwise return total value
26 return capacity > 0 ? -1 : totalValue;
27 }
28}
29
1class Solution {
2public:
3 double maxPrice(vector<vector<int>>& items, int capacity) {
4 // Sort items by value-to-weight ratio in descending order
5 // Compare (price1/weight1) with (price2/weight2) using cross multiplication
6 // to avoid floating point division: price1 * weight2 < price2 * weight1
7 sort(items.begin(), items.end(), [&](const auto& a, const auto& b) {
8 return a[1] * b[0] < a[0] * b[1];
9 });
10
11 double totalValue = 0.0;
12
13 // Greedily select items with highest value-to-weight ratio
14 for (auto& item : items) {
15 int price = item[0];
16 int weight = item[1];
17
18 // Take as much of current item as possible (fractional knapsack)
19 int amountToTake = min(weight, capacity);
20
21 // Add proportional value to total
22 // (amountToTake / weight) * price
23 totalValue += static_cast<double>(amountToTake) / weight * price;
24
25 // Update remaining capacity
26 capacity -= amountToTake;
27 }
28
29 // Return -1 if capacity not fully utilized, otherwise return total value
30 return capacity > 0 ? -1 : totalValue;
31 }
32};
33
1/**
2 * Calculates the maximum price that can be obtained by filling a knapsack with given capacity.
3 * Uses a greedy approach by sorting items by their value-to-weight ratio.
4 *
5 * @param items - Array of items where each item is [price, weight]
6 * @param capacity - The maximum weight capacity of the knapsack
7 * @returns The maximum total price if capacity is exactly filled, -1 otherwise
8 */
9function maxPrice(items: number[][], capacity: number): number {
10 // Sort items by value-to-weight ratio in descending order
11 // (a[1] * b[0] - a[0] * b[1]) is equivalent to comparing b[0]/b[1] with a[0]/a[1]
12 // This avoids division and handles the comparison in reverse order for descending sort
13 items.sort((a: number[], b: number[]) => a[1] * b[0] - a[0] * b[1]);
14
15 // Initialize the total price accumulator
16 let totalPrice: number = 0;
17
18 // Process each item in sorted order
19 for (const [price, weight] of items) {
20 // Calculate how much of this item we can take (either full item or remaining capacity)
21 const amountToTake: number = Math.min(weight, capacity);
22
23 // Add the proportional price to the total
24 // If taking partial item, calculate the fractional price
25 totalPrice += (amountToTake / weight) * price;
26
27 // Reduce the remaining capacity
28 capacity -= amountToTake;
29 }
30
31 // Return -1 if there's remaining capacity (couldn't fill knapsack exactly)
32 // Otherwise return the total price achieved
33 return capacity ? -1 : totalPrice;
34}
35
Time and Space Complexity
The time complexity is O(n Γ log n)
, where n
is the number of items. This is dominated by the sorting operation sorted(items, key=lambda x: x[1] / x[0])
, which sorts the items based on their weight-to-price ratio. The subsequent iteration through the sorted list takes O(n)
time, but this is overshadowed by the sorting cost.
The space complexity is O(log n)
. While the sorted()
function creates a new list that would typically require O(n)
space, the dominant space consideration for complexity analysis is the additional space used by the sorting algorithm itself. Python's Timsort algorithm uses O(log n)
space for its recursive call stack during the sorting process. The variables ans
, p
, w
, and v
only require O(1)
additional space.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Integer Division Instead of Float Division
Pitfall: Using integer division when calculating ratios or fractional values, which can lead to incorrect results due to truncation.
# Wrong - integer division in Python 2 or with // operator
amount_to_take = min(weight, capacity)
max_value += (amount_to_take // weight) * price # Results in 0 for fractional amounts
Solution: Always use float division to maintain precision:
# Correct - ensures float division max_value += (amount_to_take / weight) * price # Properly handles fractions
2. Incorrect Sorting Key
Pitfall: Sorting by the wrong ratio or in the wrong order. Some might accidentally sort by weight/price
in ascending order or price/weight
in ascending order.
# Wrong - sorts by weight/price ascending (lowest unit price first)
sorted_items = sorted(items, key=lambda item: item[1] / item[0])
# Wrong - sorts by price/weight ascending (also lowest unit price first)
sorted_items = sorted(items, key=lambda item: item[0] / item[1])
Solution: Sort by price/weight
ratio in descending order to prioritize highest value items:
# Correct - highest value-to-weight ratio first
sorted_items = sorted(items, key=lambda item: item[0] / item[1], reverse=True)
3. Division by Zero Error
Pitfall: Not handling items with zero weight, which causes a division by zero error when calculating the ratio.
# Crashes if any item has weight = 0
sorted_items = sorted(items, key=lambda item: item[0] / item[1], reverse=True)
Solution: Filter out or handle zero-weight items separately:
# Option 1: Filter out zero-weight items
valid_items = [item for item in items if item[1] > 0]
sorted_items = sorted(valid_items, key=lambda item: item[0] / item[1], reverse=True)
# Option 2: Handle zero-weight items with special logic
def get_ratio(item):
return float('inf') if item[1] == 0 else item[0] / item[1]
sorted_items = sorted(items, key=get_ratio, reverse=True)
4. Floating Point Precision Issues
Pitfall: Checking exact equality with floating point numbers when determining if capacity is exactly zero.
# May fail due to floating point arithmetic if capacity == 0: # Might be 0.0000000001 due to precision errors return max_value
Solution: Use an epsilon value for comparison:
EPSILON = 1e-9
if abs(capacity) < EPSILON: # More robust comparison
return max_value
5. Not Breaking Early When Capacity is Filled
Pitfall: Continuing to iterate through items even after the bag is full, which wastes computational resources.
# Inefficient - continues iterating unnecessarily
for price, weight in sorted_items:
amount_to_take = min(weight, capacity)
max_value += (amount_to_take / weight) * price
capacity -= amount_to_take
# Missing break condition
Solution: Add an early termination condition:
for price, weight in sorted_items:
amount_to_take = min(weight, capacity)
max_value += (amount_to_take / weight) * price
capacity -= amount_to_take
if capacity == 0: # Stop once bag is full
break
Which algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
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
Want a Structured Path to Master System Design Too? Donβt Miss This!