2861. Maximum Number of Alloys
Problem Description
You own a company that produces alloys using different types of metals and machines. Here's the setup:
- You have
n
different types of metals available - You have access to
k
machines that can create alloys - Each machine has its own "recipe" - it needs specific amounts of each metal type to create one alloy
- The array
composition[i][j]
tells you how many units of metal typej
thei-th
machine needs to make one alloy - You start with
stock[i]
units of metal typei
in your inventory - If you need more metal, you can buy additional units - one unit of metal type
i
costscost[i]
coins - You have a total budget of
budget
coins to spend on buying additional metals
Important constraint: All alloys must be created using the same machine. You can't mix and match - if you choose machine 1, all your alloys must be made with machine 1.
Your goal is to determine the maximum number of alloys you can create while staying within your budget.
For example, if a machine needs 2 units of metal A and 3 units of metal B to make one alloy, and you want to make 5 alloys, you'll need 10 units of metal A and 15 units of metal B total. You can use your existing stock first, then buy any additional metals needed as long as you don't exceed your budget.
The function should return the maximum number of alloys that can be created.
Intuition
Let's think about what we're trying to find. We want the maximum number of alloys we can make, and we must use exactly one machine for all alloys.
First observation: Since we must pick one machine and stick with it, we need to try each machine separately and see which one gives us the best result. This means we'll iterate through all k
machines.
For each machine, the key question becomes: "What's the maximum number of alloys I can make with this specific machine?"
Now, here's the critical insight: If we can make x
alloys with a certain budget, then we can definitely make x-1
, x-2
, ..., 1
, or 0
alloys with the same or less budget. Conversely, if we cannot afford to make x
alloys, we definitely cannot make x+1
, x+2
, or more alloys.
This monotonic property is perfect for binary search! We can search for the maximum number of alloys we can make with each machine.
For the binary search bounds:
- Lower bound:
0
alloys (we can always make zero) - Upper bound: We need to be clever here. The theoretical maximum would be limited by either our budget or our existing stock. A reasonable upper bound is
budget + stock[0]
(or any large enough value).
For each candidate number mid
during binary search, we calculate the cost:
- To make
mid
alloys with machinei
, we needmid * composition[i][j]
units of metal typej
- We already have
stock[j]
units, so we need to buymax(0, mid * composition[i][j] - stock[j])
additional units - The cost for metal type
j
ismax(0, mid * composition[i][j] - stock[j]) * cost[j]
- Total cost is the sum across all metal types
If the total cost is within budget, we can make mid
alloys, so we search for a larger number. Otherwise, we search for a smaller number.
After trying all machines with binary search, we return the maximum number of alloys found across all machines.
Learn more about Binary Search patterns.
Solution Approach
The solution implements a binary search approach for each machine to find the maximum number of alloys that can be created.
Main Algorithm Structure:
-
Initialize
ans = 0
to track the maximum number of alloys across all machines. -
Iterate through each machine's composition
c
in thecomposition
array:- Each
c
represents one machine's recipe for making an alloy
- Each
-
For each machine, perform binary search:
- Set search bounds:
l = 0
(minimum alloys) andr = budget + stock[0]
(upper bound estimate) - The upper bound is a reasonable estimate since we can't make more alloys than our budget plus existing stock would allow
- Set search bounds:
-
Binary Search Logic:
while l < r: mid = (l + r + 1) >> 1 # Calculate midpoint (using bit shift for division by 2)
- We use
(l + r + 1) >> 1
instead of(l + r) >> 1
to avoid infinite loops whenl
andr
are adjacent
- We use
-
Cost Calculation for
mid
alloys:s = sum(max(0, mid * x - y) * z for x, y, z in zip(c, stock, cost))
- For each metal type, calculate:
max(0, mid * x - y) * z
mid * x
: total units needed of this metal type to makemid
alloysmid * x - y
: additional units needed after using existing stocky
max(0, mid * x - y)
: ensures we don't have negative values (when stock exceeds needs)- Multiply by
z
(cost per unit) to get the total cost for this metal type
- Sum all costs across all metal types to get total cost
s
- For each metal type, calculate:
-
Binary Search Decision:
if s <= budget: l = mid # Can afford mid alloys, try for more else: r = mid - 1 # Cannot afford mid alloys, try fewer
-
After binary search completes for this machine,
l
contains the maximum number of alloys this machine can produce within budget. -
Update the global maximum:
ans = max(ans, l)
-
After checking all machines, return
ans
as the maximum number of alloys that can be created.
Key Implementation Details:
- The
zip(c, stock, cost)
efficiently pairs up the composition requirements, available stock, and unit costs for each metal type - The bit shift operation
>> 1
is used for efficient integer division by 2 - The
max(0, ...)
operation ensures we only count costs for metals we need to purchase, not for metals we already have sufficient stock of
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 walk through a concrete example to illustrate the solution approach.
Example:
n = 2
metal typesk = 2
machinescomposition = [[1, 1], [1, 2]]
- Machine 0 needs: 1 unit of metal 0 and 1 unit of metal 1 per alloy
- Machine 1 needs: 1 unit of metal 0 and 2 units of metal 1 per alloy
stock = [1, 1]
(we have 1 unit of each metal type)cost = [2, 3]
(metal 0 costs 2 coins/unit, metal 1 costs 3 coins/unit)budget = 10
coins
Step 1: Try Machine 0 (composition [1, 1])
Binary search for maximum alloys:
- Initial bounds:
l = 0
,r = 10 + 1 = 11
Iteration 1:
mid = (0 + 11 + 1) >> 1 = 6
- To make 6 alloys: need 6 units of metal 0, 6 units of metal 1
- Cost calculation:
- Metal 0:
max(0, 6 - 1) * 2 = 5 * 2 = 10
coins - Metal 1:
max(0, 6 - 1) * 3 = 5 * 3 = 15
coins - Total: 25 coins > 10 (budget)
- Metal 0:
- Too expensive! Set
r = 5
Iteration 2:
mid = (0 + 5 + 1) >> 1 = 3
- To make 3 alloys: need 3 units of metal 0, 3 units of metal 1
- Cost calculation:
- Metal 0:
max(0, 3 - 1) * 2 = 2 * 2 = 4
coins - Metal 1:
max(0, 3 - 1) * 3 = 2 * 3 = 6
coins - Total: 10 coins = 10 (budget)
- Metal 0:
- Affordable! Set
l = 3
Iteration 3:
mid = (3 + 5 + 1) >> 1 = 4
- To make 4 alloys: need 4 units of metal 0, 4 units of metal 1
- Cost calculation:
- Metal 0:
max(0, 4 - 1) * 2 = 3 * 2 = 6
coins - Metal 1:
max(0, 4 - 1) * 3 = 3 * 3 = 9
coins - Total: 15 coins > 10 (budget)
- Metal 0:
- Too expensive! Set
r = 3
Binary search terminates with l = 3
. Machine 0 can make maximum 3 alloys.
Step 2: Try Machine 1 (composition [1, 2])
Binary search for maximum alloys:
- Initial bounds:
l = 0
,r = 11
Iteration 1:
mid = 6
- To make 6 alloys: need 6 units of metal 0, 12 units of metal 1
- Cost calculation:
- Metal 0:
max(0, 6 - 1) * 2 = 5 * 2 = 10
coins - Metal 1:
max(0, 12 - 1) * 3 = 11 * 3 = 33
coins - Total: 43 coins > 10 (budget)
- Metal 0:
- Too expensive! Set
r = 5
Iteration 2:
mid = 3
- To make 3 alloys: need 3 units of metal 0, 6 units of metal 1
- Cost calculation:
- Metal 0:
max(0, 3 - 1) * 2 = 2 * 2 = 4
coins - Metal 1:
max(0, 6 - 1) * 3 = 5 * 3 = 15
coins - Total: 19 coins > 10 (budget)
- Metal 0:
- Too expensive! Set
r = 2
Iteration 3:
mid = 1
- To make 1 alloy: need 1 unit of metal 0, 2 units of metal 1
- Cost calculation:
- Metal 0:
max(0, 1 - 1) * 2 = 0 * 2 = 0
coins (have enough stock) - Metal 1:
max(0, 2 - 1) * 3 = 1 * 3 = 3
coins - Total: 3 coins ≤ 10 (budget)
- Metal 0:
- Affordable! Set
l = 1
Iteration 4:
mid = 2
- To make 2 alloys: need 2 units of metal 0, 4 units of metal 1
- Cost calculation:
- Metal 0:
max(0, 2 - 1) * 2 = 1 * 2 = 2
coins - Metal 1:
max(0, 4 - 1) * 3 = 3 * 3 = 9
coins - Total: 11 coins > 10 (budget)
- Metal 0:
- Too expensive! Set
r = 1
Binary search terminates with l = 1
. Machine 1 can make maximum 1 alloy.
Step 3: Return the maximum
- Machine 0: 3 alloys
- Machine 1: 1 alloy
- Maximum: 3 alloys
The answer is 3, achieved by using Machine 0 to create 3 alloys with exactly the budget of 10 coins.
Solution Implementation
1class Solution:
2 def maxNumberOfAlloys(
3 self,
4 n: int,
5 k: int,
6 budget: int,
7 composition: List[List[int]],
8 stock: List[int],
9 cost: List[int],
10 ) -> int:
11 max_alloys = 0
12
13 # Try each machine (each has different composition requirements)
14 for machine_composition in composition:
15 # Binary search for maximum number of alloys this machine can produce
16 # Left boundary: 0 alloys
17 # Right boundary: budget + initial stock (upper bound estimate)
18 left, right = 0, budget + stock[0]
19
20 while left < right:
21 # Calculate midpoint (ceiling division to avoid infinite loop)
22 mid = (left + right + 1) >> 1
23
24 # Calculate total cost to produce 'mid' alloys
25 # For each metal type:
26 # - Required amount: mid * composition_amount
27 # - Need to buy: max(0, required - stock_available)
28 # - Cost to buy: amount_to_buy * unit_cost
29 total_cost = sum(
30 max(0, mid * required - available) * unit_cost
31 for required, available, unit_cost in zip(machine_composition, stock, cost)
32 )
33
34 # Check if we can afford to produce 'mid' alloys
35 if total_cost <= budget:
36 # Can afford, try to produce more
37 left = mid
38 else:
39 # Too expensive, reduce the number of alloys
40 right = mid - 1
41
42 # Update maximum alloys that can be produced across all machines
43 max_alloys = max(max_alloys, left)
44
45 return max_alloys
46
1class Solution {
2 // Instance variables for sharing data between methods
3 private int metalCount; // Number of different metal types
4 private int budget; // Available budget for purchasing metals
5 private List<List<Integer>> composition; // Composition requirements for each machine
6 private List<Integer> stock; // Current stock of each metal type
7 private List<Integer> cost; // Cost per unit of each metal type
8
9 /**
10 * Checks if it's possible to create 'targetAlloys' number of alloys
11 * with at least one machine within the budget constraint
12 *
13 * @param targetAlloys The target number of alloys to produce
14 * @return true if possible, false otherwise
15 */
16 private boolean canProduceAlloys(long targetAlloys) {
17 // Try each machine to see if any can produce the target number of alloys
18 for (List<Integer> machineComposition : composition) {
19 long remainingBudget = budget;
20
21 // Calculate cost for all metal types needed by this machine
22 for (int metalIndex = 0; metalIndex < metalCount && remainingBudget >= 0; metalIndex++) {
23 // Calculate additional metal needed beyond current stock
24 long metalNeeded = Math.max(0,
25 machineComposition.get(metalIndex) * targetAlloys - stock.get(metalIndex));
26
27 // Deduct the cost of purchasing additional metal
28 remainingBudget -= metalNeeded * cost.get(metalIndex);
29 }
30
31 // If this machine can produce target alloys within budget, return true
32 if (remainingBudget >= 0) {
33 return true;
34 }
35 }
36
37 // No machine can produce the target number of alloys within budget
38 return false;
39 }
40
41 /**
42 * Finds the maximum number of alloys that can be produced
43 * using any single machine within the given budget
44 *
45 * @param n Number of different metal types
46 * @param k Number of machines available
47 * @param budget Available budget for purchasing metals
48 * @param composition List of metal compositions for each machine
49 * @param stock Current stock of each metal type
50 * @param cost Cost per unit of each metal type
51 * @return Maximum number of alloys that can be produced
52 */
53 public int maxNumberOfAlloys(int n, int k, int budget,
54 List<List<Integer>> composition,
55 List<Integer> stock,
56 List<Integer> cost) {
57 // Initialize instance variables
58 this.metalCount = n;
59 this.budget = budget;
60 this.composition = composition;
61 this.stock = stock;
62 this.cost = cost;
63
64 // Binary search boundaries
65 int left = -1; // Start with -1 to handle edge case where no alloys can be made
66 // Upper bound: maximum possible alloys if we only needed the first metal type
67 int right = budget / cost.get(0) + stock.get(0);
68
69 // Binary search for the maximum number of alloys
70 while (left < right) {
71 // Calculate mid point (using bit shift for efficiency)
72 // Adding 1 ensures we round up to avoid infinite loop
73 int mid = (left + right + 1) >> 1;
74
75 if (canProduceAlloys(mid)) {
76 // If we can produce 'mid' alloys, try for more
77 left = mid;
78 } else {
79 // If we cannot produce 'mid' alloys, try for fewer
80 right = mid - 1;
81 }
82 }
83
84 return left;
85 }
86}
87
1class Solution {
2public:
3 int maxNumberOfAlloys(int n, int k, int budget, vector<vector<int>>& composition, vector<int>& stock, vector<int>& cost) {
4 // Lambda function to check if we can produce 'targetAlloys' number of alloys
5 auto canProduceAlloys = [&](long long targetAlloys) {
6 // Try each machine to see if any can produce the target number of alloys
7 for (int machineIndex = 0; machineIndex < k; machineIndex++) {
8 long long remainingBudget = budget;
9 vector<int>& currentMachineComposition = composition[machineIndex];
10
11 // Check each metal type required by this machine
12 for (int metalType = 0; metalType < n && remainingBudget >= 0; metalType++) {
13 // Calculate how many units of this metal we need to buy
14 // We need (targetAlloys * composition[i][j]) total units
15 // We already have stock[j] units, so we only need to buy the difference
16 long long metalsToBuy = max(0LL, targetAlloys * currentMachineComposition[metalType] - stock[metalType]);
17
18 // Deduct the cost of buying these metals from our budget
19 remainingBudget -= metalsToBuy * cost[metalType];
20 }
21
22 // If any machine can produce the target with the given budget, return true
23 if (remainingBudget >= 0) {
24 return true;
25 }
26 }
27 // No machine can produce the target number of alloys within budget
28 return false;
29 };
30
31 // Binary search for the maximum number of alloys we can produce
32 long long left = 0;
33 long long right = budget + stock[0]; // Upper bound estimate
34
35 // Binary search to find the maximum valid number of alloys
36 while (left < right) {
37 // Calculate mid point (using +1 to avoid infinite loop when left = right - 1)
38 long long mid = (left + right + 1) >> 1;
39
40 if (canProduceAlloys(mid)) {
41 // If we can produce 'mid' alloys, try for more
42 left = mid;
43 } else {
44 // If we cannot produce 'mid' alloys, try for less
45 right = mid - 1;
46 }
47 }
48
49 return left;
50 }
51};
52
1/**
2 * Finds the maximum number of alloys that can be created given budget and material constraints.
3 * Uses binary search to find the optimal number of alloys.
4 *
5 * @param n - Number of different metal types
6 * @param k - Number of machines (each with different composition requirements)
7 * @param budget - Total budget available for purchasing additional metals
8 * @param composition - 2D array where composition[i][j] represents units of metal j needed by machine i to create one alloy
9 * @param stock - Array where stock[i] represents current stock of metal i
10 * @param cost - Array where cost[i] represents cost per unit of metal i
11 * @returns Maximum number of alloys that can be created
12 */
13function maxNumberOfAlloys(
14 n: number,
15 k: number,
16 budget: number,
17 composition: number[][],
18 stock: number[],
19 cost: number[],
20): number {
21 // Binary search bounds: minimum 0, maximum is budget plus initial stock
22 let left = 0;
23 let right = budget + stock[0];
24
25 /**
26 * Checks if it's possible to create a target number of alloys with at least one machine
27 * @param targetAlloys - The target number of alloys to check
28 * @returns true if the target number of alloys can be created, false otherwise
29 */
30 const canCreateAlloys = (targetAlloys: number): boolean => {
31 // Try each machine to see if any can produce the target number of alloys
32 for (const machineComposition of composition) {
33 let remainingBudget = budget;
34
35 // Calculate cost for this machine to produce targetAlloys
36 for (let metalIndex = 0; metalIndex < n; metalIndex++) {
37 // Calculate how many units of this metal we need to buy
38 const requiredUnits = targetAlloys * machineComposition[metalIndex];
39 const unitsToBuy = Math.max(0, requiredUnits - stock[metalIndex]);
40
41 // Deduct the cost from remaining budget
42 remainingBudget -= unitsToBuy * cost[metalIndex];
43 }
44
45 // If this machine can produce the target within budget, return true
46 if (remainingBudget >= 0) {
47 return true;
48 }
49 }
50
51 // No machine can produce the target number of alloys within budget
52 return false;
53 };
54
55 // Binary search for the maximum number of alloys
56 while (left < right) {
57 // Calculate mid point (using bit shift for integer division, biased towards upper value)
58 const mid = (left + right + 1) >> 1;
59
60 if (canCreateAlloys(mid)) {
61 // If we can create mid alloys, try for more
62 left = mid;
63 } else {
64 // If we cannot create mid alloys, try for fewer
65 right = mid - 1;
66 }
67 }
68
69 return left;
70}
71
Time and Space Complexity
Time Complexity: O(k × n × log M)
, where k
is the number of alloy types (machines), n
is the number of metal types, and M
is the upper bound for binary search. In this problem, M = budget + stock[0] ≤ 2 × 10^8
.
- The outer loop iterates through
k
different compositions (alloy types) - For each composition, binary search is performed with
O(log M)
iterations - In each binary search iteration, we calculate the cost using
sum(max(0, mid * x - y) * z for x, y, z in zip(c, stock, cost))
, which takesO(n)
time to iterate through alln
metal types - Total:
O(k) × O(log M) × O(n) = O(k × n × log M)
Space Complexity: O(1)
- The algorithm uses only a constant amount of extra space for variables like
ans
,l
,r
,mid
, ands
- The generator expression in the sum calculation doesn't create additional lists, it computes values on-the-fly
- No additional data structures are created that scale with input size
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Binary Search Upper Bound
Pitfall: Using budget + stock[0]
as the upper bound is incorrect and can lead to wrong answers. This assumes the upper bound based only on the first metal's stock, which doesn't make logical sense since we need to consider all metals.
Why it's wrong:
- Different metals have different costs and stock amounts
- The first metal's stock is arbitrary and unrelated to the actual maximum possible alloys
- This could set the upper bound too low (missing valid solutions) or unnecessarily high (inefficient)
Solution: Calculate a more accurate upper bound by considering the minimum constraint across all metals:
# Better upper bound calculation
right = budget + min(stock) # Still not perfect but better
# Or more accurately:
right = 10**9 # Use a large enough value that covers all practical cases
# Or calculate based on actual constraints:
max_possible = budget // min(cost) + sum(stock) // min(machine_composition)
2. Integer Overflow in Cost Calculation
Pitfall: When calculating mid * required
, this multiplication can cause integer overflow for large values of mid
and required
.
Example scenario:
mid = 10^9
,required = 10^5
- Product =
10^14
which might overflow in some languages
Solution: Add validation or use appropriate data types:
# Check for potential overflow before calculation
if mid > 0 and required > (10**18) // mid:
total_cost = budget + 1 # Set to exceed budget
else:
total_cost = sum(
max(0, mid * required - available) * unit_cost
for required, available, unit_cost in zip(machine_composition, stock, cost)
)
3. Binary Search Template Confusion
Pitfall: Using the wrong binary search template can lead to infinite loops or off-by-one errors.
Common mistakes:
- Using
mid = (left + right) // 2
withleft = mid
update (causes infinite loop) - Inconsistent boundary updates
Solution: Use a consistent template:
# Template for finding maximum valid value left, right = 0, upper_bound while left <= right: # Note: <= instead of < mid = (left + right) // 2 if can_produce(mid): result = mid # Store valid answer left = mid + 1 # Try for more else: right = mid - 1 # Try fewer return result
4. Not Handling Edge Cases
Pitfall: The code doesn't explicitly handle edge cases like:
- Empty composition arrays
- Zero budget with sufficient stock
- All costs being zero
Solution: Add edge case handling:
def maxNumberOfAlloys(self, n, k, budget, composition, stock, cost):
if not composition or not composition[0]:
return 0
max_alloys = 0
for machine_composition in composition:
# Check if we can make alloys with just stock (no purchase needed)
free_alloys = min(
stock[i] // machine_composition[i] if machine_composition[i] > 0 else float('inf')
for i in range(n)
)
# Then proceed with binary search for additional alloys...
A heap is a ...?
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
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
Want a Structured Path to Master System Design Too? Don’t Miss This!