Sell Diminishing-Valued Colored Balls
MedPrompt
You have an inventory of different colored balls, and there is a customer that wants orders balls of any color.
The customer weirdly values the colored balls. Each colored ball's value is the number of balls of that color you currently have in your inventory. For example, if you own 6 yellow balls, the customer would pay 6 for the first yellow ball. After the transaction, there are only 5 yellow balls left, so the next yellow ball is then valued at 5 (i.e., the value of the balls decreases as you sell more to the customer).
You are given an integer array, inventory, where inventory[i] represents the number of balls of the ith color that you initially own. You are also given an integer orders, which represents the total number of balls that the customer wants. You can sell the balls in any order.
Return the maximum total value that you can attain after selling orders colored balls. As the answer may be too large, return it modulo 109 + 7.
Example 1:
Input: inventory = [2,5], orders = 4
Output: 14
Explanation: Sell the 1st color 1 time (2) and the 2nd color 3 times (5 + 4 + 3).
The maximum total value is 2 + 5 + 4 + 3 = 14.Example 2:
Input: inventory = [3,5], orders = 6
Output: 19
Explanation: Sell the 1st color 2 times (3 + 2) and the 2nd color 4 times (5 + 4 + 3 + 2).
The maximum total value is 3 + 2 + 5 + 4 + 3 + 2 = 19.
Constraints:
1 <= inventory.length <= 1051 <= inventory[i] <= 1091 <= orders <= min(sum(inventory[i]), 109)
Approaches
3 approaches with complexity analysis and trade-offs.
This approach uses a greedy strategy. To maximize the total profit, we should always sell the most valuable ball available at any given moment. The value of a ball is equal to the number of balls of that color currently in the inventory. Therefore, the most valuable ball is always from the color with the highest count.
A max-heap (implemented as a PriorityQueue in Java) is the ideal data structure to efficiently find the color with the most balls. We can simulate the selling process by repeatedly taking the largest element from the heap, adding its value to our profit, and inserting the decremented value back into the heap. We repeat this process orders times.
Algorithm
- Create a max-heap (Priority Queue in Java) and populate it with all the counts from the
inventoryarray. - Initialize a variable
totalProfitto 0. - Loop
orderstimes:- Extract the maximum value (
currentMax) from the heap. - Add
currentMaxtototalProfit, applying modulo arithmetic at each step. - If
currentMax - 1is greater than 0, insert this new decremented value back into the heap.
- Extract the maximum value (
- After the loop finishes, return the
totalProfit.
Walkthrough
The core idea is that to maximize profit, we must always sell the most expensive ball available. The value of a ball is determined by the number of balls of its color, so we should always sell from the color that has the largest number of balls.
A max-heap is the perfect data structure for this task as it allows O(log N) retrieval of the maximum element.
The algorithm proceeds as follows:
- Initialize a max-heap and insert all the ball counts from the
inventoryarray. - Initialize a variable
totalProfitto 0. - Loop
orderstimes: a. Extract the maximum value (currentMax) from the heap. b. AddcurrentMaxtototalProfit. Remember to use modulo arithmetic to prevent overflow. c. IfcurrentMax - 1is greater than 0, insert this new decremented value back into the heap.
This process perfectly simulates selling the most valuable ball for each of the orders, but does so one by one.
import java.util.Collections;import java.util.PriorityQueue; class Solution { public int maxProfit(int[] inventory, int orders) { long MOD = 1_000_000_007; PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder()); for (int count : inventory) { maxHeap.add(count); } long totalProfit = 0; for (int i = 0; i < orders; i++) { if (maxHeap.isEmpty()) { break; } int currentMax = maxHeap.poll(); totalProfit = (totalProfit + currentMax) % MOD; if (currentMax - 1 > 0) { maxHeap.add(currentMax - 1); } } return (int) totalProfit; }}Complexity
Time
O(orders * log N), where N is `inventory.length`. Building the heap takes O(N). Each of the `orders` sales involves a heap pop (O(log N)) and potentially a heap push (O(log N)). Since `orders` can be very large, this approach is too slow.
Space
O(N), where N is the number of colors (`inventory.length`). This space is required to store the counts in the max-heap.
Trade-offs
Pros
The logic is simple and directly follows the greedy intuition.
It's easy to implement and understand.
Cons
The time complexity is directly proportional to
orders, which can be up to 10^9. This makes the approach too slow for the given constraints and will result in a Time Limit Exceeded (TLE) error on most platforms.
This approach improves upon the simulation by not selling balls one by one. Instead of iterating orders times, we can determine the final state of the inventory more directly. The core idea is to find a "cutoff" value k. We will sell all balls that have a value greater than k, and then some balls with the value k until orders are fulfilled. This cutoff value k (the minimum price of a ball we sell) can be found efficiently using binary search, as the number of balls available to sell is a monotonic function of this minimum price.
Algorithm
- Define a helper function
canSell(price)that calculates the total number of balls that can be sold if we only sell balls with a value of at leastprice. This issum(max(0, inv - (price-1)))for allinvininventory. - The number of balls we can sell is a monotonically decreasing function of
price. This allows us to use binary search. - Binary search for the largest price
p(let's call itmin_price) for whichcanSell(p)returns true (i.e., we can sell at leastordersballs). - Once
min_priceis found, calculate the total profit in two parts:- For every
inv > min_price, we sellinv - min_priceballs. Calculate the profit using the formula for an arithmetic series sum and add it to the total. - Calculate how many orders remain. Sell these remaining balls at the price of
min_price.
- For every
- Return the total profit modulo 10^9 + 7.
Walkthrough
We can observe that the number of balls we can sell is a monotonically decreasing function of the minimum price we are willing to accept. This property is key to using binary search.
We binary search for the minimum value k that any sold ball will have. The search space for k is from 1 to max(inventory).
- Binary Search: Find the largest value
k(let's call itminPrice) such that if we sell all balls with value>= k, the total number of balls sold is at leastorders. - Calculate Profit: Once we find this
minPrice, we can calculate the total profit in two parts: a. Profit from balls > minPrice: Iterate through the inventory. For each coloriwhereinventory[i] > minPrice, we sellinventory[i] - minPriceballs. The profit from this is the sum of an arithmetic series fromminPrice + 1toinventory[i]. We sum this up for all applicable colors and count the number of balls sold. b. Profit from balls == minPrice: After the first step, we calculate how many more balls we need to sell to reachorders. These remaining balls are all sold at the price ofminPrice.
The sum of an arithmetic series from a to b can be calculated efficiently. All calculations must use long to avoid overflow and handle modulo arithmetic correctly.
class Solution { long MOD = 1_000_000_007; public int maxProfit(int[] inventory, int orders) { long low = 0, high = 1_000_000_001; long minPrice = 0; // Binary search for the minimum price 'k' of a sold ball while (low < high) { long mid = high - (high - low) / 2; if (canSell(inventory, orders, mid)) { minPrice = mid; low = mid; } else { high = mid - 1; } } // Calculate profit from balls with price > minPrice long profit = 0; long ordersSold = 0; for (long count : inventory) { if (count > minPrice) { ordersSold += (count - minPrice); profit = (profit + sumFrom(minPrice + 1, count)) % MOD; } } // Calculate profit from remaining balls sold at minPrice long remainingOrders = orders - ordersSold; profit = (profit + remainingOrders * minPrice) % MOD; return (int) profit; } // Checks if we can sell 'orders' balls if the minimum price is 'price' private boolean canSell(int[] inventory, int orders, long price) { if (price == 0) return true; long count = 0; for (long inv : inventory) { count += Math.max(0, inv - (price - 1)); } return count >= orders; } // Calculates sum of arithmetic series from 'a' to 'b' private long sumFrom(long a, long b) { long count = b - a + 1; long term1 = a + b; long term2 = count; if (term1 % 2 == 0) { term1 /= 2; } else { term2 /= 2; } term1 %= MOD; term2 %= MOD; return (term1 * term2) % MOD; }}Complexity
Time
O(N * log M), where N is `inventory.length` and M is the maximum possible value in `inventory` (up to 10^9). The binary search takes `log M` steps, and each step requires iterating through the `N` items in the inventory.
Space
O(1) extra space.
Trade-offs
Pros
Significantly more efficient than the simulation approach, avoiding TLE.
Does not require modifying the input array or using complex data structures besides basic variables.
Cons
The logic is more complex than the simulation approach.
For the given constraints, it can be slightly slower than the sorting approach because
log(max_inventory_value)can be larger thanlog(N).
This is a highly efficient greedy approach that processes sales in large batches or "layers". By sorting the inventory, we can easily identify the piles with the most balls. The core insight is that we will always sell balls from the current tallest piles until their height matches the next tallest pile. We can calculate the profit from selling this entire layer of balls in one go using an arithmetic series sum formula. This avoids the one-by-one simulation and the repeated scanning of the binary search approach.
Algorithm
- Sort the
inventoryarray in ascending order. - Append a conceptual 0 to the beginning of the array for easier boundary handling.
- Iterate from the largest element downwards. Keep track of
width, the number of piles with the same current maximum height. - In each step, consider the
current_valand thenext_val(the next smaller unique value). - The number of balls we can sell to make the
widthpiles equal tonext_valiswidth * (current_val - next_val). - Case 1:
ordersis large enough. If we can sell all these balls, calculate the profit for this "layer", subtract the count fromorders, and continue. Thewidthincreases as the next pile joins the top level. - Case 2:
ordersis not large enough. We can't clear the whole layer. The remainingordersare distributed among thewidthpiles. Calculate profit from sellingorders / widthfull levels from each pile, and thenorders % widthindividual balls from the next level down. Then terminate.
Walkthrough
This approach refines the greedy strategy by processing sales in groups. After sorting the inventory in descending order, we can think of the ball counts as bars in a histogram.
- Sort: Sort the
inventoryarray. It's often easier to sort ascending and iterate from the end. - Process in Layers: We iterate through the sorted array, grouping identical-sized inventories. Let's say we have
widthpiles of sizecurrent_val, and the next largest size isnext_val. - Calculate Batch Sale: We can sell
diff = current_val - next_valballs from each of thewidthpiles before they all become sizenext_val. The total number of balls in this batch iswidth * diff. - Two Scenarios:
a. If we have enough
ordersto sell this entire batch, we calculate the profit using the arithmetic sum formula forwidthpiles, updateorders, and move to the next level. Thewidthwill increase as the piles of sizenext_valnow join the top group. b. Ifordersis smaller than the batch size, we know the process ends here. We only need to sell the remainingorders. These are distributed as evenly as possible: we sellorders / widthfull layers from each of thewidthpiles, and thenorders % widthindividual balls from the top-most remaining layer. We calculate the profit for this final partial sale and we're done.
This method is very fast because it processes potentially huge numbers of sales in a few arithmetic operations.
import java.util.Arrays; class Solution { public int maxProfit(int[] inventory, int orders) { long MOD = 1_000_000_007; Arrays.sort(inventory); int n = inventory.length; long totalProfit = 0; long currentOrders = orders; long width = 0; for (int i = n - 1; i >= 0; i--) { width++; long currentVal = inventory[i]; long nextVal = (i > 0) ? inventory[i - 1] : 0; long diff = currentVal - nextVal; if (diff == 0) { continue; } long ballsToSell = width * diff; if (currentOrders >= ballsToSell) { // Sell all balls in this layer long profit = sumFrom(nextVal + 1, currentVal); totalProfit = (totalProfit + width * profit) % MOD; currentOrders -= ballsToSell; } else { // Sell a partial layer long numFullLevels = currentOrders / width; long finalVal = currentVal - numFullLevels; long profit = sumFrom(finalVal + 1, currentVal); totalProfit = (totalProfit + width * profit) % MOD; long remainingBalls = currentOrders % width; totalProfit = (totalProfit + remainingBalls * finalVal) % MOD; currentOrders = 0; // All orders fulfilled } if (currentOrders == 0) { break; } } return (int) totalProfit; } // Calculates sum of arithmetic series from 'a' to 'b' private long sumFrom(long a, long b) { long count = b - a + 1; // sum = (a + b) * count / 2 long term1 = a + b; long term2 = count; if (term1 % 2 == 0) { term1 /= 2; } else { term2 /= 2; } term1 %= MOD; term2 %= MOD; return (term1 * term2) % MOD; }}Complexity
Time
O(N log N), dominated by the initial sort of the inventory. The subsequent loop runs at most N times.
Space
O(1) or O(log N) depending on the space used by the in-place sorting algorithm.
Trade-offs
Pros
Generally the most efficient solution for the given constraints.
Processes sales in large, aggregated batches, making it very fast.
The number of iterations is at most N, regardless of the size of
orders.
Cons
The logic for handling layers and partial layers can be tricky to implement correctly, especially with the modulo arithmetic.
Requires sorting the input, which modifies the array or requires extra space for a copy.
Solutions
Solution
class Solution {private static final int MOD = (int)1 e9 + 7;public int maxProfit(int[] inventory, int orders) { Arrays.sort(inventory); int n = inventory.length; for (int i = 0, j = n - 1; i < j; ++i, --j) { int t = inventory[i]; inventory[i] = inventory[j]; inventory[j] = t; } long ans = 0; int i = 0; while (orders > 0) { while (i < n && inventory[i] >= inventory[0]) { ++i; } int nxt = i < n ? inventory[i] : 0; int cnt = i; long x = inventory[0] - nxt; long tot = cnt * x; if (tot > orders) { int decr = orders / cnt; long a1 = inventory[0] - decr + 1, an = inventory[0]; ans += (a1 + an) * decr / 2 * cnt; ans += (a1 - 1) * (orders % cnt); } else { long a1 = nxt + 1, an = inventory[0]; ans += (a1 + an) * x / 2 * cnt; inventory[0] = nxt; } orders -= tot; ans %= MOD; } return (int)ans; }}class Solution {public: int maxProfit(vector<int> &inventory, int orders) { long ans = 0, mod = 1e9 + 7; int i = 0, n = inventory.size(); sort(inventory.rbegin(), inventory.rend()); while (orders > 0) { while (i < n && inventory[i] >= inventory[0]) { ++i; } int nxt = i < n ? inventory[i] : 0; int cnt = i; long x = inventory[0] - nxt; long tot = cnt * x; if (tot > orders) { int decr = orders / cnt; long a1 = inventory[0] - decr + 1, an = inventory[0]; ans += (a1 + an) * decr / 2 * cnt; ans += (a1 - 1) * (orders % cnt); } else { long a1 = nxt + 1, an = inventory[0]; ans += (a1 + an) * x / 2 * cnt; inventory[0] = nxt; } orders -= tot; ans %= mod; } return ans; }};class Solution: def maxProfit(self, inventory: List[int], orders: int) -> int: inventory . sort(reverse=True) mod = 10 ** 9 + 7 ans = i = 0 n = len(inventory) while orders > 0: while i < n and inventory[i] >= inventory[0]: i += 1 nxt = 0 if i < n: nxt = inventory[i] cnt = i x = inventory[0] - nxt tot = cnt * x if tot > orders: decr = orders // cnt a1, an = inventory[0] - decr + 1, inventory[0] ans += (a1 + an) * decr // 2 * cnt ans += (inventory[0] - decr) * (orders % cnt) else: a1, an = nxt + 1, inventory[0] ans += (a1 + an) * x // 2 * cnt inventory[0] = nxt orders -= tot ans %= mod return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.