Facebook Pixel

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 type j the i-th machine needs to make one alloy
  • You start with stock[i] units of metal type i in your inventory
  • If you need more metal, you can buy additional units - one unit of metal type i costs cost[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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 machine i, we need mid * composition[i][j] units of metal type j
  • We already have stock[j] units, so we need to buy max(0, mid * composition[i][j] - stock[j]) additional units
  • The cost for metal type j is max(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:

  1. Initialize ans = 0 to track the maximum number of alloys across all machines.

  2. Iterate through each machine's composition c in the composition array:

    • Each c represents one machine's recipe for making an alloy
  3. For each machine, perform binary search:

    • Set search bounds: l = 0 (minimum alloys) and r = 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
  4. 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 when l and r are adjacent
  5. 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 make mid alloys
      • mid * x - y: additional units needed after using existing stock y
      • 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
  6. 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
  7. After binary search completes for this machine, l contains the maximum number of alloys this machine can produce within budget.

  8. Update the global maximum: ans = max(ans, l)

  9. 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 Evaluator

Example Walkthrough

Let's walk through a concrete example to illustrate the solution approach.

Example:

  • n = 2 metal types
  • k = 2 machines
  • composition = [[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)
  • 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)
  • 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)
  • 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)
  • 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)
  • 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)
  • 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)
  • 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 takes O(n) time to iterate through all n 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, and s
  • 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 with left = 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...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

A heap is a ...?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More