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 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        def exceeds_budget(machine_composition: List[int], num_alloys: int) -> bool:
12            """
13            Feasible function for binary search template.
14            Returns True if producing num_alloys EXCEEDS budget (cannot afford).
15            Pattern: False, False, ..., False, True, True, True
16            We find first True (first unaffordable), answer is first_true_index - 1.
17            """
18            total_cost = sum(
19                max(0, num_alloys * required - available) * unit_cost
20                for required, available, unit_cost in zip(machine_composition, stock, cost)
21            )
22            return total_cost > budget
23
24        max_alloys = 0
25
26        # Try each machine (each has different composition requirements)
27        for machine_composition in composition:
28            # Binary search for maximum number of alloys this machine can produce
29            # Search space: [0, upper_bound]
30            # Pattern: can_afford=True, True, ..., True, False, False (for original condition)
31            # Inverted: exceeds_budget=False, False, ..., False, True, True
32            # Find first True index (first position that exceeds budget)
33            left, right = 0, budget + stock[0]
34            first_true_index = -1
35
36            while left <= right:
37                mid = left + (right - left) // 2
38                if exceeds_budget(machine_composition, mid):
39                    first_true_index = mid
40                    right = mid - 1
41                else:
42                    left = mid + 1
43
44            # Maximum affordable alloys is first_true_index - 1
45            # If first_true_index is -1, all values in range are affordable
46            if first_true_index == -1:
47                machine_max = budget + stock[0]
48            else:
49                machine_max = first_true_index - 1
50
51            max_alloys = max(max_alloys, machine_max)
52
53        return max_alloys
54
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     * Feasible function for binary search template.
11     * Returns true if producing targetAlloys EXCEEDS budget for ALL machines.
12     * Pattern: false, false, ..., false, true, true, true
13     * We find first true (first unaffordable for all machines).
14     */
15    private boolean exceedsBudgetForAllMachines(long targetAlloys) {
16        // Try each machine to see if any can produce the target within budget
17        for (List<Integer> machineComposition : composition) {
18            long totalCost = 0;
19
20            // Calculate cost for all metal types needed by this machine
21            for (int metalIndex = 0; metalIndex < metalCount; metalIndex++) {
22                long metalNeeded = Math.max(0,
23                    machineComposition.get(metalIndex) * targetAlloys - stock.get(metalIndex));
24                totalCost += metalNeeded * cost.get(metalIndex);
25            }
26
27            // If this machine can produce within budget, return false (affordable)
28            if (totalCost <= budget) {
29                return false;
30            }
31        }
32
33        // All machines exceed budget
34        return true;
35    }
36
37    /**
38     * Finds the maximum number of alloys that can be produced
39     * using any single machine within the given budget
40     */
41    public int maxNumberOfAlloys(int n, int k, int budget,
42                                  List<List<Integer>> composition,
43                                  List<Integer> stock,
44                                  List<Integer> cost) {
45        // Initialize instance variables
46        this.metalCount = n;
47        this.budget = budget;
48        this.composition = composition;
49        this.stock = stock;
50        this.cost = cost;
51
52        // Binary search using template: find first true index
53        int left = 0;
54        int right = budget + stock.get(0);
55        int firstTrueIndex = -1;
56
57        while (left <= right) {
58            int mid = left + (right - left) / 2;
59            if (exceedsBudgetForAllMachines(mid)) {
60                firstTrueIndex = mid;
61                right = mid - 1;
62            } else {
63                left = mid + 1;
64            }
65        }
66
67        // Maximum affordable is firstTrueIndex - 1
68        // If firstTrueIndex is -1, all values in range are affordable
69        if (firstTrueIndex == -1) {
70            return budget + stock.get(0);
71        }
72        return firstTrueIndex - 1;
73    }
74}
75
1class Solution {
2public:
3    int maxNumberOfAlloys(int n, int k, int budget, vector<vector<int>>& composition, vector<int>& stock, vector<int>& cost) {
4        // Feasible function: returns true if producing targetAlloys EXCEEDS budget for ALL machines
5        // Pattern: false, false, ..., false, true, true, true
6        auto exceedsBudgetForAllMachines = [&](long long targetAlloys) {
7            for (int machineIndex = 0; machineIndex < k; machineIndex++) {
8                long long totalCost = 0;
9                vector<int>& currentMachineComposition = composition[machineIndex];
10
11                for (int metalType = 0; metalType < n; metalType++) {
12                    long long metalsToBuy = max(0LL, targetAlloys * currentMachineComposition[metalType] - stock[metalType]);
13                    totalCost += metalsToBuy * cost[metalType];
14                }
15
16                // If this machine can produce within budget, return false
17                if (totalCost <= budget) {
18                    return false;
19                }
20            }
21            // All machines exceed budget
22            return true;
23        };
24
25        // Binary search using template: find first true index
26        int left = 0;
27        int right = budget + stock[0];
28        int firstTrueIndex = -1;
29
30        while (left <= right) {
31            int mid = left + (right - left) / 2;
32            if (exceedsBudgetForAllMachines(mid)) {
33                firstTrueIndex = mid;
34                right = mid - 1;
35            } else {
36                left = mid + 1;
37            }
38        }
39
40        // Maximum affordable is firstTrueIndex - 1
41        if (firstTrueIndex == -1) {
42            return budget + stock[0];
43        }
44        return firstTrueIndex - 1;
45    }
46};
47
1/**
2 * Finds the maximum number of alloys that can be created given budget and material constraints.
3 * Uses binary search template to find the optimal number of alloys.
4 */
5function maxNumberOfAlloys(
6    n: number,
7    k: number,
8    budget: number,
9    composition: number[][],
10    stock: number[],
11    cost: number[],
12): number {
13    /**
14     * Feasible function for binary search template.
15     * Returns true if producing targetAlloys EXCEEDS budget for ALL machines.
16     * Pattern: false, false, ..., false, true, true, true
17     */
18    const exceedsBudgetForAllMachines = (targetAlloys: number): boolean => {
19        for (const machineComposition of composition) {
20            let totalCost = 0;
21
22            for (let metalIndex = 0; metalIndex < n; metalIndex++) {
23                const requiredUnits = targetAlloys * machineComposition[metalIndex];
24                const unitsToBuy = Math.max(0, requiredUnits - stock[metalIndex]);
25                totalCost += unitsToBuy * cost[metalIndex];
26            }
27
28            // If this machine can produce within budget, return false
29            if (totalCost <= budget) {
30                return false;
31            }
32        }
33
34        // All machines exceed budget
35        return true;
36    };
37
38    // Binary search using template: find first true index
39    let left = 0;
40    let right = budget + stock[0];
41    let firstTrueIndex = -1;
42
43    while (left <= right) {
44        const mid = Math.floor((left + right) / 2);
45        if (exceedsBudgetForAllMachines(mid)) {
46            firstTrueIndex = mid;
47            right = mid - 1;
48        } else {
49            left = mid + 1;
50        }
51    }
52
53    // Maximum affordable is firstTrueIndex - 1
54    if (firstTrueIndex === -1) {
55        return budget + stock[0];
56    }
57    return firstTrueIndex - 1;
58}
59

Solution Approach

The solution uses the binary search template to find the maximum number of alloys that can be created within budget.

Understanding the Feasible Pattern:

For this problem, we want to find the maximum number of alloys. The affordability pattern looks like:

  • For 0 alloys: affordable (True)
  • For 1 alloy: affordable (True)
  • ...
  • For k alloys: affordable (True)
  • For k+1 alloys: NOT affordable (False)

To use our template which finds the first True, we invert the condition. Define exceedsBudget(x) as "producing x alloys exceeds budget for ALL machines":

  • Pattern becomes: False, False, ..., False, True, True, True
  • We find the first True index (first unaffordable amount)
  • Maximum affordable = firstTrueIndex - 1

Binary Search Template Applied:

left, right = 0, budget + stock[0]
first_true_index = -1

while left <= right:
    mid = left + (right - left) // 2
    if exceeds_budget(mid):  # feasible condition
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1

return first_true_index - 1 if first_true_index != -1 else upper_bound

Cost Calculation:

For each candidate mid alloys, we check if ANY machine can produce within budget:

total_cost = sum(max(0, mid * required - available) * unit_cost
                 for required, available, unit_cost in zip(composition, stock, cost))
return total_cost > budget  # True if exceeds budget
  • mid * required: total units needed of this metal type
  • max(0, mid * required - available): additional units to buy after using stock
  • Multiply by unit cost and sum across all metals

Why This Works:

The key insight is that cost is monotonically increasing with the number of alloys. More alloys always cost at least as much as fewer alloys. This creates the clear boundary we need for binary search.

After finding the first unaffordable amount, we return firstTrueIndex - 1 as the maximum affordable number of alloys.

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.

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. Using Wrong Binary Search Template Variant

Pitfall: Using while left < right with right = mid instead of the standard template with while left <= right and right = mid - 1.

Common incorrect pattern:

# WRONG: Non-standard template
while left < right:
    mid = (left + right + 1) >> 1
    if can_produce(mid):
        left = mid
    else:
        right = mid - 1
return left

Solution: Use the standard template with first_true_index:

# CORRECT: Standard template
left, right = 0, upper_bound
first_true_index = -1

while left <= right:
    mid = left + (right - left) // 2
    if exceeds_budget(mid):  # feasible condition (inverted)
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1

return first_true_index - 1 if first_true_index != -1 else upper_bound

2. Incorrect Binary Search Upper Bound

Pitfall: Using budget + stock[0] as the upper bound only considers the first metal's stock, which may be insufficient.

Why it's problematic:

  • Different metals have different costs and stock amounts
  • The first metal's stock is arbitrary and may not reflect actual constraints

Solution: Use a sufficiently large upper bound or calculate more carefully:

right = budget + min(stock)  # More conservative
# Or simply use a large enough value:
right = 10**9

3. Integer Overflow in Cost Calculation

Pitfall: When calculating mid * required, this multiplication can overflow for large values.

Example: mid = 10^9, required = 10^5 → Product = 10^14

Solution: Use appropriate data types (long long in C++/Java, Python handles this automatically).

4. Not Handling Edge Cases

Pitfall: Missing edge cases like empty composition arrays or zero budget.

Solution: Add explicit edge case handling:

if not composition or not composition[0]:
    return 0
Loading...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which type of traversal does breadth first search do?


Recommended Readings

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

Load More